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)
46static const bigtime_t kMaxBufferLateness = 20000LL;
47
48
49VideoConsumer::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
81VideoConsumer::~VideoConsumer()
82{
83	Quit();
84	DeleteBuffers();
85}
86
87
88BMediaAddOn*
89VideoConsumer::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
98void
99VideoConsumer::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
118status_t
119VideoConsumer::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
136status_t
137VideoConsumer::HandleMessage(int32 message, const void* data, size_t size)
138{
139	return B_OK;
140}
141
142
143void
144VideoConsumer::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
160void
161VideoConsumer::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
171status_t
172VideoConsumer::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
262void
263VideoConsumer::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
288void
289VideoConsumer::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
301void
302VideoConsumer::SetTryOverlay(bool tryOverlay)
303{
304	fTryOverlay = tryOverlay;
305}
306
307
308status_t
309VideoConsumer::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
350void
351VideoConsumer::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
381status_t
382VideoConsumer::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
445status_t
446VideoConsumer::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
466void
467VideoConsumer::DisposeInputCookie(int32 /*cookie*/)
468{
469}
470
471
472status_t
473VideoConsumer::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
487status_t
488VideoConsumer::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
506void
507VideoConsumer::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
541void
542VideoConsumer::_SetPerformanceTimeBase(bigtime_t performanceTime)
543{
544	fPerformanceTimeBase = performanceTime;
545}
546
547
548void
549VideoConsumer::_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
617void
618VideoConsumer::_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