xref: /haiku/src/apps/mediaplayer/media_node_framework/video/VideoConsumer.cpp (revision aa3083e086e5a929c061c72983e09d916c548a38)
1 /*	Copyright (c) 1998-99, Be Incorporated, All Rights Reserved.
2  *	Distributed under the terms of the Be Sample Code license.
3  *
4  *	Copyright (c) 2000-2008, Ingo Weinhold <ingo_weinhold@gmx.de>,
5  *	Copyright (c) 2000-2008, Stephan Aßmus <superstippi@gmx.de>,
6  *	All Rights Reserved. Distributed under the terms of the MIT license.
7  */
8 #include "VideoConsumer.h"
9 
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <unistd.h>
14 
15 #include <Buffer.h>
16 #include <BufferGroup.h>
17 #include <NodeInfo.h>
18 #include <Bitmap.h>
19 #include <View.h>
20 #include <scheduler.h>
21 #include <TimeSource.h>
22 #include <MediaRoster.h>
23 
24 #include "ColorSpaceToString.h"
25 #include "NodeManager.h"
26 #include "VideoTarget.h"
27 
28 
29 // debugging
30 //#define TRACE_VIDEO_CONSUMER
31 #ifdef TRACE_VIDEO_CONSUMER
32 # define TRACE(x...)		printf(x)
33 # define PROGRESS(x...)		printf(x)
34 # define FUNCTION(x...)		printf(x)
35 # define LOOP(x...)			printf(x)
36 # define ERROR(x...)		fprintf(stderr, x)
37 #else
38 # define TRACE(x...)
39 # define PROGRESS(x...)
40 # define FUNCTION(x...)
41 # define LOOP(x...)
42 # define ERROR(x...)		fprintf(stderr, x)
43 #endif
44 
45 #define M1 ((double)1000000.0)
46 static const bigtime_t kMaxBufferLateness = 20000LL;
47 
48 
49 VideoConsumer::VideoConsumer(const char* name, BMediaAddOn* addon,
50 		const uint32 internal_id, NodeManager* manager,
51 		VideoTarget* target)
52 	: BMediaNode(name),
53 	  BMediaEventLooper(),
54 	  BBufferConsumer(B_MEDIA_RAW_VIDEO),
55 	  fInternalID(internal_id),
56 	  fAddOn(addon),
57 	  fConnectionActive(false),
58 	  fMyLatency(3000),
59 	  fOurBuffers(false),
60 	  fBuffers(NULL),
61 	  fManager(manager),
62 	  fTargetLock(),
63 	  fTarget(target),
64 	  fLastBufferIndex(-1),
65 	  fTryOverlay(true)
66 {
67 	FUNCTION("VideoConsumer::VideoConsumer\n");
68 
69 	AddNodeKind(B_PHYSICAL_OUTPUT);
70 	SetEventLatency(0);
71 
72 	for (uint32 i = 0; i < kBufferCount; i++) {
73 		fBitmap[i] = NULL;
74 		fBufferMap[i] = NULL;
75 	}
76 
77 	SetPriority(B_DISPLAY_PRIORITY);
78 }
79 
80 
81 VideoConsumer::~VideoConsumer()
82 {
83 	Quit();
84 	DeleteBuffers();
85 }
86 
87 
88 BMediaAddOn*
89 VideoConsumer::AddOn(int32* cookie) const
90 {
91 	FUNCTION("VideoConsumer::AddOn\n");
92 	// do the right thing if we're ever used with an add-on
93 	*cookie = fInternalID;
94 	return fAddOn;
95 }
96 
97 
98 void
99 VideoConsumer::NodeRegistered()
100 {
101 	FUNCTION("VideoConsumer::NodeRegistered\n");
102 	fIn.destination.port = ControlPort();
103 	fIn.destination.id = 0;
104 	fIn.source = media_source::null;
105 	fIn.format.type = B_MEDIA_RAW_VIDEO;
106 	// wild cards yet
107 	fIn.format.u.raw_video = media_raw_video_format::wildcard;
108 	fIn.format.u.raw_video.interlace = 1;
109 	fIn.format.u.raw_video.display.format = B_NO_COLOR_SPACE;
110 	fIn.format.u.raw_video.display.bytes_per_row = 0;
111 	fIn.format.u.raw_video.display.line_width = 0;
112 	fIn.format.u.raw_video.display.line_count = 0;
113 
114 	Run();
115 }
116 
117 
118 status_t
119 VideoConsumer::RequestCompleted(const media_request_info& info)
120 {
121 	FUNCTION("VideoConsumer::RequestCompleted\n");
122 	switch(info.what) {
123 		case media_request_info::B_SET_OUTPUT_BUFFERS_FOR:
124 			if (info.status != B_OK)
125 				ERROR("VideoConsumer::RequestCompleted: Not using our "
126 					"buffers!\n");
127 			break;
128 
129 		default:
130 			break;
131 	}
132 	return B_OK;
133 }
134 
135 
136 status_t
137 VideoConsumer::HandleMessage(int32 message, const void* data, size_t size)
138 {
139 	return B_OK;
140 }
141 
142 
143 void
144 VideoConsumer::BufferReceived(BBuffer* buffer)
145 {
146 	LOOP("VideoConsumer::Buffer #%" B_PRId32 " received\n", buffer->ID());
147 
148 	if (RunState() == B_STOPPED) {
149 		buffer->Recycle();
150 		return;
151 	}
152 
153 	media_timed_event event(buffer->Header()->start_time,
154 		BTimedEventQueue::B_HANDLE_BUFFER, buffer,
155 		BTimedEventQueue::B_RECYCLE_BUFFER);
156 	EventQueue()->AddEvent(event);
157 }
158 
159 
160 void
161 VideoConsumer::ProducerDataStatus(const media_destination& forWhom,
162 	int32 status, bigtime_t atMediaTime)
163 {
164 	FUNCTION("VideoConsumer::ProducerDataStatus\n");
165 
166 	if (forWhom != fIn.destination)
167 		return;
168 }
169 
170 
171 status_t
172 VideoConsumer::CreateBuffers(const media_format& format)
173 {
174 	FUNCTION("VideoConsumer::CreateBuffers\n");
175 
176 	// delete any old buffers
177 	DeleteBuffers();
178 
179 	status_t status = B_OK;
180 
181 	// create a buffer group
182 	uint32 width = format.u.raw_video.display.line_width;
183 	uint32 height = format.u.raw_video.display.line_count;
184 	color_space colorSpace = format.u.raw_video.display.format;
185 	PROGRESS("VideoConsumer::CreateBuffers - Width = %" B_PRIu32 " - "
186 		"Height = %" B_PRIu32 " - Colorspace = %d\n",
187 		width, height, colorSpace);
188 
189 	fBuffers = new BBufferGroup();
190 	status = fBuffers->InitCheck();
191 	if (B_OK != status) {
192 		ERROR("VideoConsumer::CreateBuffers - ERROR CREATING BUFFER GROUP\n");
193 		return status;
194 	}
195 
196 	// and attach the bitmaps to the buffer group
197 	BRect bounds(0, 0, width - 1, height - 1);
198 	for (uint32 i = 0; i < kBufferCount; i++) {
199 		// figure out the bitmap creation flags
200 		uint32 bitmapFlags = 0;
201 		if (fTryOverlay) {
202 			// try to use hardware overlay
203 			bitmapFlags |= B_BITMAP_WILL_OVERLAY;
204 			if (i == 0)
205 				bitmapFlags |= B_BITMAP_RESERVE_OVERLAY_CHANNEL;
206 		} else
207 			bitmapFlags = B_BITMAP_IS_LOCKED;
208 
209 		fBitmap[i] = new BBitmap(bounds, bitmapFlags, colorSpace);
210 		status = fBitmap[i]->InitCheck();
211 		if (status >= B_OK) {
212 			buffer_clone_info info;
213 
214 			uint8* bits = (uint8*)fBitmap[i]->Bits();
215 			info.area = area_for(bits);
216 			area_info bitmapAreaInfo;
217 			status = get_area_info(info.area, &bitmapAreaInfo);
218 			if (status != B_OK) {
219 				fprintf(stderr, "VideoConsumer::CreateBuffers() - "
220 					"get_area_info(): %s\n", strerror(status));
221 				return status;
222 			}
223 
224 //printf("area info for bitmap %ld (%p):\n", i, fBitmap[i]->Bits());
225 //printf("        area: %ld\n", bitmapAreaInfo.area);
226 //printf("        size: %ld\n", bitmapAreaInfo.size);
227 //printf("        lock: %ld\n", bitmapAreaInfo.lock);
228 //printf("  protection: %ld\n", bitmapAreaInfo.protection);
229 //printf("    ram size: %ld\n", bitmapAreaInfo.ram_size);
230 //printf("  copy_count: %ld\n", bitmapAreaInfo.copy_count);
231 //printf("   out_count: %ld\n", bitmapAreaInfo.out_count);
232 //printf("     address: %p\n", bitmapAreaInfo.address);
233 
234 			info.offset = bits - (uint8*)bitmapAreaInfo.address;
235 			info.size = (size_t)fBitmap[i]->BitsLength();
236 			info.flags = 0;
237 			info.buffer = 0;
238 				// the media buffer id
239 
240 			BBuffer* buffer = NULL;
241 			if ((status = fBuffers->AddBuffer(info, &buffer)) != B_OK) {
242 				ERROR("VideoConsumer::CreateBuffers - ERROR ADDING BUFFER "
243 					"TO GROUP (%" B_PRId32 "): %s\n", i, strerror(status));
244 				return status;
245 			} else {
246 				PROGRESS("VideoConsumer::CreateBuffers - SUCCESSFUL ADD "
247 					"BUFFER TO GROUP\n");
248 			}
249 			fBufferMap[i] = buffer;
250 		} else {
251 			ERROR("VideoConsumer::CreateBuffers - ERROR CREATING VIDEO RING "
252 				"BUFFER (Index %" B_PRId32 " Width %" B_PRId32 " Height %"
253 				B_PRId32 " Colorspace %d: %s\n", i, width, height, colorSpace,
254 				strerror(status));
255 			return status;
256 		}
257 	}
258 
259 	FUNCTION("VideoConsumer::CreateBuffers - EXIT\n");
260 	return status;
261 }
262 
263 
264 void
265 VideoConsumer::DeleteBuffers()
266 {
267 	FUNCTION("VideoConsumer::DeleteBuffers\n");
268 	if (fBuffers) {
269 		fTargetLock.Lock();
270 		if (fLastBufferIndex >= 0) {
271 			if (fTarget)
272 				fTarget->SetBitmap(NULL);
273 			fLastBufferIndex = -1;
274 		}
275 		fTargetLock.Unlock();
276 
277 		delete fBuffers;
278 		fBuffers = NULL;
279 
280 		for (uint32 i = 0; i < kBufferCount; i++) {
281 			snooze(20000);
282 			delete fBitmap[i];
283 			fBitmap[i] = NULL;
284 		}
285 	}
286 	FUNCTION("VideoConsumer::DeleteBuffers - EXIT\n");
287 }
288 
289 
290 void
291 VideoConsumer::SetTarget(VideoTarget* target)
292 {
293 	fTargetLock.Lock();
294 	if (fTarget)
295 		fTarget->SetBitmap(NULL);
296 	fTarget = target;
297 	if (fTarget && fLastBufferIndex >= 0)
298 		fTarget->SetBitmap(fBitmap[fLastBufferIndex]);
299 	fTargetLock.Unlock();
300 }
301 
302 
303 void
304 VideoConsumer::SetTryOverlay(bool tryOverlay)
305 {
306 	fTryOverlay = tryOverlay;
307 }
308 
309 
310 status_t
311 VideoConsumer::Connected(const media_source& producer,
312 	const media_destination& where, const media_format& format,
313 	media_input* outInput)
314 {
315 	FUNCTION("VideoConsumer::Connected\n");
316 
317 	fIn.source = producer;
318 	fIn.format = format;
319 	fIn.node = Node();
320 	sprintf(fIn.name, "Video Consumer");
321 	*outInput = fIn;
322 
323 	uint32 userData = 0;
324 	int32 changeTag = 1;
325 	status_t ret = CreateBuffers(format);
326 	if (ret == B_OK) {
327 		// TODO: With overlay bitmaps, there seems to be a problem with
328 		// mapping the BBitmap areas into the BBuffers. Until that is fixed,
329 		// don't enable a shared BBufferGroup.
330 		if (!fTryOverlay) {
331 			ret = SetOutputBuffersFor(producer, fIn.destination,
332 				fBuffers, &userData, &changeTag, true);
333 			if (ret != B_OK)
334 				ERROR("SetOutputBuffersFor() failed: %s\n", strerror(ret));
335 		}
336 		fIn.format.u.raw_video.display.bytes_per_row
337 			= fBitmap[0]->BytesPerRow();
338 	} else {
339 		ERROR("VideoConsumer::Connected - COULDN'T CREATE BUFFERS\n");
340 		return ret;
341 	}
342 
343 	*outInput = fIn;
344 		// bytes per row might have changed
345 	fConnectionActive = true;
346 
347 	FUNCTION("VideoConsumer::Connected - EXIT\n");
348 	return B_OK;
349 }
350 
351 
352 void
353 VideoConsumer::Disconnected(const media_source& producer,
354 	const media_destination& where)
355 {
356 	FUNCTION("VideoConsumer::Disconnected\n");
357 
358 	if (where != fIn.destination || producer != fIn.source)
359 		return;
360 
361 	// reclaim our buffers
362 	int32 changeTag = 0;
363 	SetOutputBuffersFor(producer, fIn.destination, NULL, NULL, &changeTag,
364 		false);
365 	if (fOurBuffers) {
366 		status_t reclaimError = fBuffers->ReclaimAllBuffers();
367 		if (reclaimError != B_OK) {
368 			fprintf(stderr, "VideoConsumer::Disconnected() - Failed to "
369 				"reclaim our buffers: %s\n", strerror(reclaimError));
370 		}
371 	}
372 	// disconnect the connection
373 	fIn.source = media_source::null;
374 	fConnectionActive = false;
375 
376 	// Unset the target's bitmap. Just to be safe -- since it is usually
377 	// done when the stop event arrives, but someone may disonnect
378 	// without stopping us before.
379 	_UnsetTargetBuffer();
380 }
381 
382 
383 status_t
384 VideoConsumer::AcceptFormat(const media_destination& dest, media_format* format)
385 {
386 	FUNCTION("VideoConsumer::AcceptFormat\n");
387 
388 	if (dest != fIn.destination) {
389 		ERROR("VideoConsumer::AcceptFormat - BAD DESTINATION\n");
390 		return B_MEDIA_BAD_DESTINATION;
391 	}
392 
393 	if (format->type == B_MEDIA_NO_TYPE)
394 		format->type = B_MEDIA_RAW_VIDEO;
395 
396 	if (format->type != B_MEDIA_RAW_VIDEO) {
397 		ERROR("VideoConsumer::AcceptFormat - BAD FORMAT\n");
398 		return B_MEDIA_BAD_FORMAT;
399 	}
400 
401 	if (format->u.raw_video.display.format
402 			!= media_raw_video_format::wildcard.display.format) {
403 		uint32 flags = 0;
404 		bool supported = bitmaps_support_space(
405 			format->u.raw_video.display.format, &flags);
406 #ifndef HAIKU_TARGET_PLATFORM_HAIKU
407 		// GRRR! BeOS implementation claims not
408 		// to support these formats, while they work just fine.
409 		switch (format->u.raw_video.display.format) {
410 			case B_YCbCr422:
411 			case B_YCbCr411:
412 			case B_YCbCr444:
413 			case B_YCbCr420:
414 				supported = true;
415 				break;
416 			default:
417 				break;
418 		}
419 #endif
420 		if (!supported) {
421 			// cannot create bitmaps with such a color space
422 			ERROR("AcceptFormat - unsupported color space for BBitmaps "
423 				"(%s)!\n",
424 				color_space_to_string(format->u.raw_video.display.format));
425 			return B_MEDIA_BAD_FORMAT;
426 		}
427 		if (!fTryOverlay && (flags & B_VIEWS_SUPPORT_DRAW_BITMAP) == 0) {
428 			// BViews do not support drawing such a bitmap
429 			ERROR("AcceptFormat - BViews cannot draw bitmaps in given "
430 				"colorspace (%s)!\n",
431 				color_space_to_string(format->u.raw_video.display.format));
432 			return B_MEDIA_BAD_FORMAT;
433 		}
434 	}
435 
436 	#ifdef TRACE_VIDEO_CONSUMER
437 		char string[256];
438 		string[0] = 0;
439 		string_for_format(*format, string, 256);
440 		FUNCTION("VideoConsumer::AcceptFormat: %s\n", string);
441 	#endif
442 
443 	return B_OK;
444 }
445 
446 
447 status_t
448 VideoConsumer::GetNextInput(int32* cookie, media_input* outInput)
449 {
450 	FUNCTION("VideoConsumer::GetNextInput\n");
451 
452 	// custom build a destination for this connection
453 	// put connection number in id
454 
455 	if (*cookie < 1) {
456 		fIn.node = Node();
457 		fIn.destination.id = *cookie;
458 		sprintf(fIn.name, "Video Consumer");
459 		*outInput = fIn;
460 		(*cookie)++;
461 		return B_OK;
462 	} else {
463 		return B_MEDIA_BAD_DESTINATION;
464 	}
465 }
466 
467 
468 void
469 VideoConsumer::DisposeInputCookie(int32 /*cookie*/)
470 {
471 }
472 
473 
474 status_t
475 VideoConsumer::GetLatencyFor(const media_destination& whom,
476 	bigtime_t* _latency, media_node_id* _timeSource)
477 {
478 	FUNCTION("VideoConsumer::GetLatencyFor\n");
479 
480 	if (whom != fIn.destination)
481 		return B_MEDIA_BAD_DESTINATION;
482 
483 	*_latency = fMyLatency;
484 	*_timeSource = TimeSource()->ID();
485 	return B_OK;
486 }
487 
488 
489 status_t
490 VideoConsumer::FormatChanged(const media_source& producer,
491 	const media_destination& consumer, int32 fromChangeCount,
492 	const media_format& format)
493 {
494 	FUNCTION("VideoConsumer::FormatChanged\n");
495 
496 	if (consumer != fIn.destination)
497 		return B_MEDIA_BAD_DESTINATION;
498 
499 	if (producer != fIn.source)
500 		return B_MEDIA_BAD_SOURCE;
501 
502 	fIn.format = format;
503 
504 	return CreateBuffers(format);
505 }
506 
507 
508 void
509 VideoConsumer::HandleEvent(const media_timed_event* event, bigtime_t lateness,
510 	bool realTimeEvent)
511 {
512 	LOOP("VideoConsumer::HandleEvent\n");
513 
514 	switch (event->type) {
515 		case BTimedEventQueue::B_START:
516 			PROGRESS("VideoConsumer::HandleEvent - START\n");
517 			_SetPerformanceTimeBase(event->event_time);
518 			break;
519 		case BTimedEventQueue::B_WARP:
520 		case BTimedEventQueue::B_SEEK:
521 			PROGRESS("VideoConsumer::HandleEvent - WARP or SEEK\n");
522 			_SetPerformanceTimeBase(event->bigdata);
523 			break;
524 
525 		case BTimedEventQueue::B_STOP:
526 			PROGRESS("VideoConsumer::HandleEvent - STOP\n");
527 			EventQueue()->FlushEvents(event->event_time, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER);
528 			// unset the target's bitmap
529 			_UnsetTargetBuffer();
530 			break;
531 
532 		case BTimedEventQueue::B_HANDLE_BUFFER:
533 			LOOP("VideoConsumer::HandleEvent - HANDLE BUFFER\n");
534 			_HandleBuffer(static_cast<BBuffer*>(event->pointer));
535 			break;
536 		default:
537 			ERROR("VideoConsumer::HandleEvent - BAD EVENT\n");
538 			break;
539 	}
540 }
541 
542 
543 void
544 VideoConsumer::_SetPerformanceTimeBase(bigtime_t performanceTime)
545 {
546 	fPerformanceTimeBase = performanceTime;
547 }
548 
549 
550 void
551 VideoConsumer::_HandleBuffer(BBuffer* buffer)
552 {
553 	if (RunState() != B_STARTED || !fConnectionActive) {
554 		TRACE("RunState() != B_STARTED\n");
555 		buffer->Recycle();
556 		return;
557 	}
558 
559 	// See if this is one of our BBitmap buffers
560 	uint32 index = 0;
561 	fOurBuffers = true;
562 	while (index < kBufferCount) {
563 		if (buffer->ID() == fBufferMap[index]->ID())
564 			break;
565 		else
566 			index++;
567 	}
568 	if (index == kBufferCount) {
569 		// Buffers belong to consumer
570 		// NOTE: We maintain this in a member variable, since we still need
571 		// to recycle this buffer later on, in case it was the last buffer
572 		// received before shutting down.
573 		fOurBuffers = false;
574 		index = (fLastBufferIndex + 1) % kBufferCount;
575 	}
576 
577 	bool recycle = true;
578 	bigtime_t now = TimeSource()->Now();
579 	if (RunMode() == B_OFFLINE
580 		|| now < buffer->Header()->start_time + kMaxBufferLateness) {
581 		// Only display the buffer if it's not too late, or if we are
582 		// in B_OFFLINE run-mode.
583 		if (!fOurBuffers) {
584 			memcpy(fBitmap[index]->Bits(), buffer->Data(),
585 				fBitmap[index]->BitsLength());
586 		}
587 		bigtime_t tooEarly = buffer->Header()->start_time - now;
588 		if (tooEarly > 3000)
589 			snooze(tooEarly);
590 
591 		fTargetLock.Lock();
592 		if (fTarget) {
593 			fTarget->SetBitmap(fBitmap[index]);
594 			if (fOurBuffers) {
595 				// recycle the previous but not the current buffer
596 				if (fLastBufferIndex >= 0)
597 					fBufferMap[fLastBufferIndex]->Recycle();
598 				recycle = false;
599 			}
600 			fLastBufferIndex = index;
601 		}
602 		fTargetLock.Unlock();
603 	} else {
604 		// Drop the buffer if it's too late.
605 		if (fManager->LockWithTimeout(10000) == B_OK) {
606 			fManager->FrameDropped();
607 			fManager->Unlock();
608 		}
609 		PROGRESS("VideoConsumer::HandleEvent - DROPPED FRAME\n"
610 			"   start_time: %" B_PRIdBIGTIME ", current: %" B_PRIdBIGTIME ", "
611 			"latency: %" B_PRIdBIGTIME "\n",  buffer->Header()->start_time,
612 			TimeSource()->Now(), SchedulingLatency());
613 	}
614 	if (recycle)
615 		buffer->Recycle();
616 }
617 
618 
619 void
620 VideoConsumer::_UnsetTargetBuffer()
621 {
622 	fTargetLock.Lock();
623 	if (fLastBufferIndex >= 0) {
624 		if (fTarget)
625 			fTarget->SetBitmap(NULL);
626 		if (fOurBuffers)
627 			fBufferMap[fLastBufferIndex]->Recycle();
628 		fLastBufferIndex = -1;
629 	}
630 	fTargetLock.Unlock();
631 }
632