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(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
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 #%" 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
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 = %" 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
264void
265VideoConsumer::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
290void
291VideoConsumer::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
303void
304VideoConsumer::SetTryOverlay(bool tryOverlay)
305{
306	fTryOverlay = tryOverlay;
307}
308
309
310status_t
311VideoConsumer::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
352void
353VideoConsumer::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
383status_t
384VideoConsumer::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
447status_t
448VideoConsumer::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
468void
469VideoConsumer::DisposeInputCookie(int32 /*cookie*/)
470{
471}
472
473
474status_t
475VideoConsumer::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
489status_t
490VideoConsumer::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
508void
509VideoConsumer::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
543void
544VideoConsumer::_SetPerformanceTimeBase(bigtime_t performanceTime)
545{
546	fPerformanceTimeBase = performanceTime;
547}
548
549
550void
551VideoConsumer::_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
619void
620VideoConsumer::_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