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