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