xref: /haiku/src/apps/mediaplayer/media_node_framework/video/VideoConsumer.cpp (revision 220d04022750f40f8bac8f01fa551211e28d04f2)
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 #%ld 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 = %ld - Height = %ld - "
186 		"Colorspace = %d\n", width, height, colorSpace);
187 
188 	fBuffers = new BBufferGroup();
189 	status = fBuffers->InitCheck();
190 	if (B_OK != status) {
191 		ERROR("VideoConsumer::CreateBuffers - ERROR CREATING BUFFER GROUP\n");
192 		return status;
193 	}
194 
195 	// and attach the bitmaps to the buffer group
196 	BRect bounds(0, 0, width - 1, height - 1);
197 	for (uint32 i = 0; i < kBufferCount; i++) {
198 		// figure out the bitmap creation flags
199 		uint32 bitmapFlags = 0;
200 		if (fTryOverlay) {
201 			// try to use hardware overlay
202 			bitmapFlags |= B_BITMAP_WILL_OVERLAY;
203 			if (i == 0)
204 				bitmapFlags |= B_BITMAP_RESERVE_OVERLAY_CHANNEL;
205 		} else
206 			bitmapFlags = B_BITMAP_IS_LOCKED;
207 
208 		fBitmap[i] = new BBitmap(bounds, bitmapFlags, colorSpace);
209 		status = fBitmap[i]->InitCheck();
210 		if (status >= B_OK) {
211 			buffer_clone_info info;
212 
213 			uint8* bits = (uint8*)fBitmap[i]->Bits();
214 			info.area = area_for(bits);
215 			area_info bitmapAreaInfo;
216 			status = get_area_info(info.area, &bitmapAreaInfo);
217 			if (status != B_OK) {
218 				fprintf(stderr, "VideoConsumer::CreateBuffers() - "
219 					"get_area_info(): %s\n", strerror(status));
220 				return status;
221 			}
222 
223 //printf("area info for bitmap %ld (%p):\n", i, fBitmap[i]->Bits());
224 //printf("        area: %ld\n", bitmapAreaInfo.area);
225 //printf("        size: %ld\n", bitmapAreaInfo.size);
226 //printf("        lock: %ld\n", bitmapAreaInfo.lock);
227 //printf("  protection: %ld\n", bitmapAreaInfo.protection);
228 //printf("    ram size: %ld\n", bitmapAreaInfo.ram_size);
229 //printf("  copy_count: %ld\n", bitmapAreaInfo.copy_count);
230 //printf("   out_count: %ld\n", bitmapAreaInfo.out_count);
231 //printf("     address: %p\n", bitmapAreaInfo.address);
232 
233 			info.offset = bits - (uint8*)bitmapAreaInfo.address;
234 			info.size = (size_t)fBitmap[i]->BitsLength();
235 			info.flags = 0;
236 			info.buffer = 0;
237 				// the media buffer id
238 
239 			BBuffer* buffer = NULL;
240 			if ((status = fBuffers->AddBuffer(info, &buffer)) != B_OK) {
241 				ERROR("VideoConsumer::CreateBuffers - ERROR ADDING BUFFER "
242 					"TO GROUP (%" B_PRId32 "): %s\n", i, strerror(status));
243 				return status;
244 			} else {
245 				PROGRESS("VideoConsumer::CreateBuffers - SUCCESSFUL ADD "
246 					"BUFFER TO GROUP\n");
247 			}
248 			fBufferMap[i] = buffer;
249 		} else {
250 			ERROR("VideoConsumer::CreateBuffers - ERROR CREATING VIDEO RING "
251 				"BUFFER (Index %" B_PRId32 " Width %" B_PRId32 " Height %"
252 				B_PRId32 " Colorspace %d: %s\n", i, width, height, colorSpace,
253 				strerror(status));
254 			return status;
255 		}
256 	}
257 
258 	FUNCTION("VideoConsumer::CreateBuffers - EXIT\n");
259 	return status;
260 }
261 
262 
263 void
264 VideoConsumer::DeleteBuffers()
265 {
266 	FUNCTION("VideoConsumer::DeleteBuffers\n");
267 	if (fBuffers) {
268 		fTargetLock.Lock();
269 		if (fLastBufferIndex >= 0) {
270 			if (fTarget)
271 				fTarget->SetBitmap(NULL);
272 			fLastBufferIndex = -1;
273 		}
274 		fTargetLock.Unlock();
275 
276 		delete fBuffers;
277 		fBuffers = NULL;
278 
279 		for (uint32 i = 0; i < kBufferCount; i++) {
280 			snooze(20000);
281 			delete fBitmap[i];
282 			fBitmap[i] = NULL;
283 		}
284 	}
285 	FUNCTION("VideoConsumer::DeleteBuffers - EXIT\n");
286 }
287 
288 
289 void
290 VideoConsumer::SetTarget(VideoTarget* target)
291 {
292 	fTargetLock.Lock();
293 	if (fTarget)
294 		fTarget->SetBitmap(NULL);
295 	fTarget = target;
296 	if (fTarget && fLastBufferIndex >= 0)
297 		fTarget->SetBitmap(fBitmap[fLastBufferIndex]);
298 	fTargetLock.Unlock();
299 }
300 
301 
302 void
303 VideoConsumer::SetTryOverlay(bool tryOverlay)
304 {
305 	fTryOverlay = tryOverlay;
306 }
307 
308 
309 status_t
310 VideoConsumer::Connected(const media_source& producer,
311 	const media_destination& where, const media_format& format,
312 	media_input* outInput)
313 {
314 	FUNCTION("VideoConsumer::Connected\n");
315 
316 	fIn.source = producer;
317 	fIn.format = format;
318 	fIn.node = Node();
319 	sprintf(fIn.name, "Video Consumer");
320 	*outInput = fIn;
321 
322 	uint32 userData = 0;
323 	int32 changeTag = 1;
324 	status_t ret = CreateBuffers(format);
325 	if (ret == B_OK) {
326 		// TODO: With overlay bitmaps, there seems to be a problem with
327 		// mapping the BBitmap areas into the BBuffers. Until that is fixed,
328 		// don't enable a shared BBufferGroup.
329 		if (!fTryOverlay) {
330 			ret = SetOutputBuffersFor(producer, fIn.destination,
331 				fBuffers, &userData, &changeTag, true);
332 			if (ret != B_OK)
333 				ERROR("SetOutputBuffersFor() failed: %s\n", strerror(ret));
334 		}
335 		fIn.format.u.raw_video.display.bytes_per_row
336 			= fBitmap[0]->BytesPerRow();
337 	} else {
338 		ERROR("VideoConsumer::Connected - COULDN'T CREATE BUFFERS\n");
339 		return ret;
340 	}
341 
342 	*outInput = fIn;
343 		// bytes per row might have changed
344 	fConnectionActive = true;
345 
346 	FUNCTION("VideoConsumer::Connected - EXIT\n");
347 	return B_OK;
348 }
349 
350 
351 void
352 VideoConsumer::Disconnected(const media_source& producer,
353 	const media_destination& where)
354 {
355 	FUNCTION("VideoConsumer::Disconnected\n");
356 
357 	if (where != fIn.destination || producer != fIn.source)
358 		return;
359 
360 	// reclaim our buffers
361 	int32 changeTag = 0;
362 	SetOutputBuffersFor(producer, fIn.destination, NULL, NULL, &changeTag,
363 		false);
364 	if (fOurBuffers) {
365 		status_t reclaimError = fBuffers->ReclaimAllBuffers();
366 		if (reclaimError != B_OK) {
367 			fprintf(stderr, "VideoConsumer::Disconnected() - Failed to "
368 				"reclaim our buffers: %s\n", strerror(reclaimError));
369 		}
370 	}
371 	// disconnect the connection
372 	fIn.source = media_source::null;
373 	fConnectionActive = false;
374 
375 	// Unset the target's bitmap. Just to be safe -- since it is usually
376 	// done when the stop event arrives, but someone may disonnect
377 	// without stopping us before.
378 	_UnsetTargetBuffer();
379 }
380 
381 
382 status_t
383 VideoConsumer::AcceptFormat(const media_destination& dest, media_format* format)
384 {
385 	FUNCTION("VideoConsumer::AcceptFormat\n");
386 
387 	if (dest != fIn.destination) {
388 		ERROR("VideoConsumer::AcceptFormat - BAD DESTINATION\n");
389 		return B_MEDIA_BAD_DESTINATION;
390 	}
391 
392 	if (format->type == B_MEDIA_NO_TYPE)
393 		format->type = B_MEDIA_RAW_VIDEO;
394 
395 	if (format->type != B_MEDIA_RAW_VIDEO) {
396 		ERROR("VideoConsumer::AcceptFormat - BAD FORMAT\n");
397 		return B_MEDIA_BAD_FORMAT;
398 	}
399 
400 	if (format->u.raw_video.display.format
401 			!= media_raw_video_format::wildcard.display.format) {
402 		uint32 flags = 0;
403 		bool supported = bitmaps_support_space(
404 			format->u.raw_video.display.format, &flags);
405 #ifndef HAIKU_TARGET_PLATFORM_HAIKU
406 		// GRRR! BeOS implementation claims not
407 		// to support these formats, while they work just fine.
408 		switch (format->u.raw_video.display.format) {
409 			case B_YCbCr422:
410 			case B_YCbCr411:
411 			case B_YCbCr444:
412 			case B_YCbCr420:
413 				supported = true;
414 				break;
415 			default:
416 				break;
417 		}
418 #endif
419 		if (!supported) {
420 			// cannot create bitmaps with such a color space
421 			ERROR("AcceptFormat - unsupported color space for BBitmaps "
422 				"(%s)!\n",
423 				color_space_to_string(format->u.raw_video.display.format));
424 			return B_MEDIA_BAD_FORMAT;
425 		}
426 		if (!fTryOverlay && (flags & B_VIEWS_SUPPORT_DRAW_BITMAP) == 0) {
427 			// BViews do not support drawing such a bitmap
428 			ERROR("AcceptFormat - BViews cannot draw bitmaps in given "
429 				"colorspace (%s)!\n",
430 				color_space_to_string(format->u.raw_video.display.format));
431 			return B_MEDIA_BAD_FORMAT;
432 		}
433 	}
434 
435 	#ifdef TRACE_VIDEO_CONSUMER
436 		char string[256];
437 		string[0] = 0;
438 		string_for_format(*format, string, 256);
439 		FUNCTION("VideoConsumer::AcceptFormat: %s\n", string);
440 	#endif
441 
442 	return B_OK;
443 }
444 
445 
446 status_t
447 VideoConsumer::GetNextInput(int32* cookie, media_input* outInput)
448 {
449 	FUNCTION("VideoConsumer::GetNextInput\n");
450 
451 	// custom build a destination for this connection
452 	// put connection number in id
453 
454 	if (*cookie < 1) {
455 		fIn.node = Node();
456 		fIn.destination.id = *cookie;
457 		sprintf(fIn.name, "Video Consumer");
458 		*outInput = fIn;
459 		(*cookie)++;
460 		return B_OK;
461 	} else {
462 		return B_MEDIA_BAD_DESTINATION;
463 	}
464 }
465 
466 
467 void
468 VideoConsumer::DisposeInputCookie(int32 /*cookie*/)
469 {
470 }
471 
472 
473 status_t
474 VideoConsumer::GetLatencyFor(const media_destination& whom,
475 	bigtime_t* _latency, media_node_id* _timeSource)
476 {
477 	FUNCTION("VideoConsumer::GetLatencyFor\n");
478 
479 	if (whom != fIn.destination)
480 		return B_MEDIA_BAD_DESTINATION;
481 
482 	*_latency = fMyLatency;
483 	*_timeSource = TimeSource()->ID();
484 	return B_OK;
485 }
486 
487 
488 status_t
489 VideoConsumer::FormatChanged(const media_source& producer,
490 	const media_destination& consumer, int32 fromChangeCount,
491 	const media_format& format)
492 {
493 	FUNCTION("VideoConsumer::FormatChanged\n");
494 
495 	if (consumer != fIn.destination)
496 		return B_MEDIA_BAD_DESTINATION;
497 
498 	if (producer != fIn.source)
499 		return B_MEDIA_BAD_SOURCE;
500 
501 	fIn.format = format;
502 
503 	return CreateBuffers(format);
504 }
505 
506 
507 void
508 VideoConsumer::HandleEvent(const media_timed_event* event, bigtime_t lateness,
509 	bool realTimeEvent)
510 {
511 	LOOP("VideoConsumer::HandleEvent\n");
512 
513 	switch (event->type) {
514 		case BTimedEventQueue::B_START:
515 			PROGRESS("VideoConsumer::HandleEvent - START\n");
516 			_SetPerformanceTimeBase(event->event_time);
517 			break;
518 		case BTimedEventQueue::B_WARP:
519 		case BTimedEventQueue::B_SEEK:
520 			PROGRESS("VideoConsumer::HandleEvent - WARP or SEEK\n");
521 			_SetPerformanceTimeBase(event->bigdata);
522 			break;
523 
524 		case BTimedEventQueue::B_STOP:
525 			PROGRESS("VideoConsumer::HandleEvent - STOP\n");
526 			EventQueue()->FlushEvents(event->event_time, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER);
527 			// unset the target's bitmap
528 			_UnsetTargetBuffer();
529 			break;
530 
531 		case BTimedEventQueue::B_HANDLE_BUFFER:
532 			LOOP("VideoConsumer::HandleEvent - HANDLE BUFFER\n");
533 			_HandleBuffer(static_cast<BBuffer*>(event->pointer));
534 			break;
535 		default:
536 			ERROR("VideoConsumer::HandleEvent - BAD EVENT\n");
537 			break;
538 	}
539 }
540 
541 
542 void
543 VideoConsumer::_SetPerformanceTimeBase(bigtime_t performanceTime)
544 {
545 	fPerformanceTimeBase = performanceTime;
546 }
547 
548 
549 void
550 VideoConsumer::_HandleBuffer(BBuffer* buffer)
551 {
552 	if (RunState() != B_STARTED || !fConnectionActive) {
553 		TRACE("RunState() != B_STARTED\n");
554 		buffer->Recycle();
555 		return;
556 	}
557 
558 	// See if this is one of our BBitmap buffers
559 	uint32 index = 0;
560 	fOurBuffers = true;
561 	while (index < kBufferCount) {
562 		if (buffer->ID() == fBufferMap[index]->ID())
563 			break;
564 		else
565 			index++;
566 	}
567 	if (index == kBufferCount) {
568 		// Buffers belong to consumer
569 		// NOTE: We maintain this in a member variable, since we still need
570 		// to recycle this buffer later on, in case it was the last buffer
571 		// received before shutting down.
572 		fOurBuffers = false;
573 		index = (fLastBufferIndex + 1) % kBufferCount;
574 	}
575 
576 	bool recycle = true;
577 	bigtime_t now = TimeSource()->Now();
578 	if (RunMode() == B_OFFLINE
579 		|| now < buffer->Header()->start_time + kMaxBufferLateness) {
580 		// Only display the buffer if it's not too late, or if we are
581 		// in B_OFFLINE run-mode.
582 		if (!fOurBuffers) {
583 			memcpy(fBitmap[index]->Bits(), buffer->Data(),
584 				fBitmap[index]->BitsLength());
585 		}
586 		bigtime_t tooEarly = buffer->Header()->start_time - now;
587 		if (tooEarly > 3000)
588 			snooze(tooEarly);
589 
590 		fTargetLock.Lock();
591 		if (fTarget) {
592 			fTarget->SetBitmap(fBitmap[index]);
593 			if (fOurBuffers) {
594 				// recycle the previous but not the current buffer
595 				if (fLastBufferIndex >= 0)
596 					fBufferMap[fLastBufferIndex]->Recycle();
597 				recycle = false;
598 			}
599 			fLastBufferIndex = index;
600 		}
601 		fTargetLock.Unlock();
602 	} else {
603 		// Drop the buffer if it's too late.
604 		if (fManager->LockWithTimeout(10000) == B_OK) {
605 			fManager->FrameDropped();
606 			fManager->Unlock();
607 		}
608 		PROGRESS("VideoConsumer::HandleEvent - DROPPED FRAME\n"
609 			"   start_time: %lld, current: %lld, latency: %lld\n",
610 			buffer->Header()->start_time, TimeSource()->Now(),
611 			SchedulingLatency());
612 	}
613 	if (recycle)
614 		buffer->Recycle();
615 }
616 
617 
618 void
619 VideoConsumer::_UnsetTargetBuffer()
620 {
621 	fTargetLock.Lock();
622 	if (fLastBufferIndex >= 0) {
623 		if (fTarget)
624 			fTarget->SetBitmap(NULL);
625 		if (fOurBuffers)
626 			fBufferMap[fLastBufferIndex]->Recycle();
627 		fLastBufferIndex = -1;
628 	}
629 	fTargetLock.Unlock();
630 }
631