1/*
2 * FireWire DV media addon for Haiku
3 *
4 * Copyright (c) 2008, JiSheng Zhang (jszhang3@mail.ustc.edu.cn)
5 * Distributed under the terms of the MIT License.
6 *
7 * Based on DVB media addon
8 * Copyright (c) 2004-2007 Marcus Overhagen <marcus@overhagen.de>
9 */
10
11
12#include "FireWireDVNode.h"
13
14#include <fcntl.h>
15#include <malloc.h>
16#include <math.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <sys/uio.h>
21#include <unistd.h>
22
23#include <Autolock.h>
24#include <Buffer.h>
25#include <BufferGroup.h>
26#include <Debug.h>
27#include <Directory.h>
28#include <Entry.h>
29#include <MediaRoster.h>
30#include <ParameterWeb.h>
31#include <Path.h>
32#include <TimeSource.h>
33#include <String.h>
34
35#include "FireWireDVNode.h"
36#include "FireWireCard.h"
37#include "debug.h"
38
39#define REVISION		"unknown"
40#define VERSION			"1.0"
41#define BUILD	 		__DATE__ " " __TIME__
42
43// debugging
44#ifdef TRACE
45#	undef TRACE
46#endif
47//#define TRACE_FIREWIRE_NODE
48#ifdef TRACE_FIREWIRE_NODE
49#	define TRACE(x...)		printf(x)
50#else
51#	define TRACE(x...)
52#endif
53
54#define RETURN_IF_ERROR(expr) { status_t e = (expr); if (e != B_OK) return e; }
55
56#define M_REFRESH_PARAMETER_WEB 		(BTimedEventQueue::B_USER_EVENT + 1)
57
58FireWireDVNode::FireWireDVNode(BMediaAddOn* addon, const char* name,
59	int32 internal_id, FireWireCard* card)
60	: BMediaNode(name),
61	BBufferProducer(B_MEDIA_ENCODED_VIDEO),
62	BControllable(),
63	BMediaEventLooper(),
64	fOutputEnabledEncVideo(false),
65	fCard(card),
66	fCaptureThreadsActive(false),
67	fThreadIdCardReader(-1),
68	fTerminateThreads(false),
69	fBufferGroupEncVideo(0),
70	fCaptureActive(false)
71{
72	CALLED();
73
74	AddNodeKind(B_PHYSICAL_INPUT);
75//	AddNodeKind(B_PHYSICAL_OUTPUT);
76
77	fInternalID = internal_id;
78	fAddOn = addon;
79
80	fInitStatus = B_OK;
81
82	fDefaultFormatEncVideo.type = B_MEDIA_ENCODED_VIDEO;
83}
84
85
86FireWireDVNode::~FireWireDVNode()
87{
88	CALLED();
89
90	StopCapture();
91	fWeb = NULL;
92}
93
94
95/* BMediaNode */
96BMediaAddOn*
97FireWireDVNode::AddOn(int32* internal_id) const
98{
99	if (internal_id)
100		*internal_id = fInternalID;
101	return fAddOn;
102}
103
104
105status_t
106FireWireDVNode::HandleMessage(int32 message, const void* data, size_t size)
107{
108	return B_ERROR;
109}
110
111
112void
113FireWireDVNode::Preroll()
114{
115	/* This hook may be called before the node is started to give the hardware
116	 * a chance to start. */
117}
118
119
120void
121FireWireDVNode::SetTimeSource(BTimeSource* time_source)
122{
123	CALLED();
124}
125
126
127void
128FireWireDVNode::SetRunMode(run_mode mode)
129{
130	CALLED();
131	TRACE("FireWireDVNode::SetRunMode(%d)\n", mode);
132}
133
134
135/* BMediaEventLooper */
136void
137FireWireDVNode::NodeRegistered()
138{
139	CALLED();
140
141	fOutputEncVideo.node = Node();
142	fOutputEncVideo.source.port = ControlPort();
143	fOutputEncVideo.source.id = 0;
144	fOutputEncVideo.destination = media_destination::null;
145	fOutputEncVideo.format = fDefaultFormatEncVideo;
146	strcpy(fOutputEncVideo.name, "encoded video");
147
148	RefreshParameterWeb();
149
150	SetPriority(B_REAL_TIME_PRIORITY);
151	Run();
152}
153
154
155void
156FireWireDVNode::HandleEvent(const media_timed_event* event,
157	bigtime_t lateness, bool realTimeEvent)
158{
159
160	switch(event->type)
161	{
162		case M_REFRESH_PARAMETER_WEB:
163			RefreshParameterWeb();
164			break;
165		case BTimedEventQueue::B_START:
166			HandleStart(event->event_time);
167			break;
168		case BTimedEventQueue::B_STOP:
169			HandleStop();
170			break;
171		case BTimedEventQueue::B_WARP:
172			HandleTimeWarp(event->bigdata);
173			break;
174		case BTimedEventQueue::B_SEEK:
175			HandleSeek(event->bigdata);
176			break;
177		case BTimedEventQueue::B_HANDLE_BUFFER:
178		case BTimedEventQueue::B_DATA_STATUS:
179		case BTimedEventQueue::B_PARAMETER:
180		default:
181			TRACE("FireWireDVNode::HandleEvent: Unhandled event -- %lx\n", event->type);
182			break;
183	}
184}
185
186
187/* BBufferProducer */
188status_t
189FireWireDVNode::FormatChangeRequested(const media_source& source,
190	const media_destination& destination, media_format* io_format,
191	int32* _deprecated_)
192{
193	CALLED();
194
195	// we don't support any other formats, so we just reject any format changes.
196	return B_ERROR;
197}
198
199
200status_t
201FireWireDVNode::GetNextOutput(int32* cookie, media_output* out_output)
202{
203	CALLED();
204
205	if (*cookie == 0) {
206		*out_output = fOutputEncVideo;
207		*cookie += 1;
208		return B_OK;
209	} else {
210		return B_BAD_INDEX;
211	}
212}
213
214
215status_t
216FireWireDVNode::DisposeOutputCookie(int32 cookie)
217{
218	CALLED();
219	// do nothing because we don't use the cookie for anything special
220	return B_OK;
221}
222
223
224status_t
225FireWireDVNode::SetBufferGroup(const media_source& source, BBufferGroup* group)
226{
227	CALLED();
228	return B_ERROR;
229}
230
231
232status_t
233FireWireDVNode::VideoClippingChanged(const media_source& for_source,
234	int16 num_shorts, int16* clip_data,
235	const media_video_display_info& display, int32* _deprecated_)
236{
237	CALLED();
238	return B_ERROR;
239}
240
241
242status_t
243FireWireDVNode::GetLatency(bigtime_t* out_latency)
244{
245	CALLED();
246
247	*out_latency = EventLatency() + SchedulingLatency();
248	return B_OK;
249}
250
251
252status_t
253FireWireDVNode::FormatSuggestionRequested(
254	media_type type, int32 quality, media_format* format)
255{
256	CALLED();
257
258	if (format == NULL) {
259		fprintf(stderr, "\tERROR - NULL format pointer passed in!\n");
260		return B_BAD_VALUE;
261	}
262
263	// this is the format we'll be returning (our preferred format)
264	*format = fDefaultFormatEncVideo;
265
266	// a wildcard type is okay; we can specialize it
267	if (type == B_MEDIA_UNKNOWN_TYPE)
268		type = B_MEDIA_ENCODED_VIDEO;
269
270	if (type != B_MEDIA_ENCODED_VIDEO)
271		return B_MEDIA_BAD_FORMAT;
272
273	return B_OK;
274}
275
276
277status_t
278FireWireDVNode::FormatProposal(const media_source& source,
279	media_format* format)
280{
281	CALLED();
282	/* The connection process:
283	 * we are here => BBufferProducer::FormatProposal
284	 *                BBufferConsumer::AcceptFormat
285	 *                BBufferProducer::PrepareToConnect
286	 *                BBufferConsumer::Connected
287	 *                BBufferProducer::Connect
288	 *
289	 * What we need to do:
290	 * - if the format contains a wildcard AND we have a requirement for that
291	 *   field, set it to the value we need.
292	 * - if a field has a value that is not wildcard and not supported by us,
293	 *   we don't change it, and return B_MEDIA_BAD_FORMAT
294	 * - after we are done, the format may still contain wildcards.
295	 */
296
297	if (source.port != ControlPort()) {
298		fprintf(stderr, "FireWireDVNode::FormatProposal returning "
299			"B_MEDIA_BAD_SOURCE\n");
300		return B_MEDIA_BAD_SOURCE;
301	}
302
303	media_type requestedType = format->type;
304	*format = fDefaultFormatEncVideo;
305
306	if (requestedType != B_MEDIA_UNKNOWN_TYPE
307		&& requestedType != B_MEDIA_ENCODED_VIDEO) {
308		fprintf(stderr, "FireWireDVNode::FormatProposal returning "
309			"B_MEDIA_BAD_FORMAT\n");
310		return B_MEDIA_BAD_FORMAT;
311	}
312
313	// encoded video or wildcard type, either is okay by us
314	return B_OK;
315}
316
317
318status_t
319FireWireDVNode::PrepareToConnect(const media_source& source,
320	const media_destination& destination, media_format* format,
321	media_source* out_source, char* out_name)
322{
323	/* The connection process:
324	 *                BBufferProducer::FormatProposal
325	 *                BBufferConsumer::AcceptFormat
326	 * we are here => BBufferProducer::PrepareToConnect
327	 *                BBufferConsumer::Connected
328	 *                BBufferProducer::Connect
329	 *
330	 * At this point, the consumer's AcceptFormat() method has been called,
331	 * and that node has potentially changed the proposed format. It may
332	 * also have left wildcards in the format. PrepareToConnect()
333	 * *must* fully specialize the format before returning!
334	 */
335
336	CALLED();
337	// is the source valid?
338	if (source.port != ControlPort() &&
339			fCard->DetectRecvFn() != B_OK) {
340		fprintf(stderr, "FireWireDVNode::PrepareToConnect returning "
341			"B_MEDIA_BAD_SOURCE\n");
342		return B_MEDIA_BAD_SOURCE;
343	}
344
345	// are we already connected?
346	if (fOutputEncVideo.destination != media_destination::null)
347		return B_MEDIA_ALREADY_CONNECTED;
348
349	// the format may not yet be fully specialized (the consumer might have
350	// passed back some wildcards).  Finish specializing it now, and return an
351	// error if we don't support the requested format.
352	if (format->type != B_MEDIA_RAW_AUDIO) {
353		fprintf(stderr, "\tnon-raw-audio format?!\n");
354		return B_MEDIA_BAD_FORMAT;
355	}
356
357	// reserve the connection by setting destination
358	// set the output's format to the new format
359	fOutputEncVideo.destination = destination;
360	fOutputEncVideo.format = *format;
361
362	// set source and suggest a name
363	*out_source = source;
364	strcpy(out_name, "encoded video");
365
366	return B_OK;
367}
368
369
370void
371FireWireDVNode::Connect(status_t error, const media_source& source,
372	const media_destination& destination, const media_format& format,
373	char* io_name)
374{
375	/* The connection process:
376	 *                BBufferProducer::FormatProposal
377	 *                BBufferConsumer::AcceptFormat
378	 *                BBufferProducer::PrepareToConnect
379	 *                BBufferConsumer::Connected
380	 * we are here => BBufferProducer::Connect
381	 */
382
383	CALLED();
384
385	if (error != B_OK) {
386		TRACE("Error during connecting\n");
387		// if an error occured, unreserve the connection
388		fOutputEncVideo.destination = media_destination::null;
389		fOutputEncVideo.format = fDefaultFormatEncVideo;
390		return;
391	}
392
393	// Since the destination is allowed to be changed by the
394	// consumer, the one we got in PrepareToConnect() is no
395	// longer correct, and must be updated here.
396	fOutputEncVideo.destination = destination;
397	fOutputEncVideo.format = format;
398
399	// if the connection has no name, we set it now
400	if (strlen(io_name) == 0)
401		strcpy(io_name, "encoded video");
402
403	#ifdef DEBUG
404		bigtime_t latency;
405		media_node_id ts;
406		if (B_OK != FindLatencyFor(destination, &latency, &ts))
407			TRACE("FindLatencyFor failed\n");
408		else
409			TRACE("downstream latency %lld\n", latency);
410	#endif
411}
412
413
414void
415FireWireDVNode::Disconnect(const media_source &source,
416	const media_destination& destination)
417{
418	CALLED();
419
420	// unreserve the connection
421	fOutputEncVideo.destination = media_destination::null;
422	fOutputEncVideo.format = fDefaultFormatEncVideo;
423}
424
425
426void
427FireWireDVNode::LateNoticeReceived(const media_source& source,
428	bigtime_t how_much, bigtime_t performance_time)
429{
430	TRACE("FireWireDVNode::LateNoticeReceived %lld late at %lld\n", how_much, performance_time);
431}
432
433
434void
435FireWireDVNode::EnableOutput(const media_source& source, bool enabled,
436	int32* _deprecated_)
437{
438	CALLED();
439	fOutputEnabledEncVideo = enabled;
440}
441
442
443void
444FireWireDVNode::AdditionalBufferRequested(const media_source& source,
445	media_buffer_id prev_buffer, bigtime_t prev_time,
446	const media_seek_tag* prev_tag)
447{
448	CALLED();
449	// we don't support offline mode
450	return;
451}
452
453
454/* FireWireDVNode */
455void
456FireWireDVNode::HandleTimeWarp(bigtime_t performance_time)
457{
458	TRACE("FireWireDVNode::HandleTimeWarp at %lld\n", performance_time);
459}
460
461
462void
463FireWireDVNode::HandleSeek(bigtime_t performance_time)
464{
465	TRACE("FireWireDVNode::HandleSeek at %lld\n", performance_time);
466}
467
468
469void
470FireWireDVNode::HandleStart(bigtime_t performance_time)
471{
472	CALLED();
473	StartCapture();
474}
475
476
477void
478FireWireDVNode::HandleStop(void)
479{
480	CALLED();
481	StopCapture();
482}
483
484
485status_t
486FireWireDVNode::StartCapture()
487{
488	CALLED();
489
490	if (fCaptureActive)
491		return B_OK;
492
493	RETURN_IF_ERROR(StopCaptureThreads());
494
495	RETURN_IF_ERROR(StartCaptureThreads());
496
497	fCaptureActive = true;
498
499	RefreshParameterWeb();
500
501	return B_OK;
502}
503
504
505status_t
506FireWireDVNode::StopCapture()
507{
508	CALLED();
509	if (!fCaptureActive)
510		return B_OK;
511
512	StopCaptureThreads();
513
514	fCaptureActive = false;
515	return B_OK;
516}
517
518
519status_t
520FireWireDVNode::StartCaptureThreads()
521{
522	CALLED();
523
524	if (fCaptureThreadsActive)
525		return B_OK;
526
527	fTerminateThreads = false;
528
529	fThreadIdCardReader = spawn_thread(_card_reader_thread_, "FireWire DV reader", 120, this);
530	resume_thread(fThreadIdCardReader);
531
532	fCaptureThreadsActive = true;
533	return B_OK;
534}
535
536
537status_t
538FireWireDVNode::StopCaptureThreads()
539{
540	CALLED();
541
542	if (!fCaptureThreadsActive)
543		return B_OK;
544
545	fTerminateThreads = true;
546
547	status_t dummy; // NULL as parameter does not work
548	wait_for_thread(fThreadIdCardReader, &dummy);
549
550	fCaptureThreadsActive = false;
551	return B_OK;
552}
553
554
555int32
556FireWireDVNode::_card_reader_thread_(void* arg)
557{
558	static_cast<FireWireDVNode *>(arg)->card_reader_thread();
559	return 0;
560}
561
562
563void
564FireWireDVNode::card_reader_thread()
565{
566	status_t err;
567	size_t rbufsize;
568	int rcount;
569
570	fCard->GetBufInfo(&rbufsize, &rcount);
571	delete fBufferGroupEncVideo;
572	fBufferGroupEncVideo = new BBufferGroup(rbufsize, rcount);
573	while (!fTerminateThreads) {
574		void *data, *end;
575		ssize_t sizeUsed = fCard->Read(&data);
576		if (sizeUsed < 0) {
577			TRACE("FireWireDVNode::%s: %s\n", __FUNCTION__,
578				strerror(sizeUsed));
579			continue;
580		}
581
582		end = (char*)data + sizeUsed;
583
584		while (data < end) {
585			BBuffer* buf = fBufferGroupEncVideo->RequestBuffer(rbufsize, 10000);
586			if (!buf) {
587				TRACE("OutVideo: request buffer timout\n");
588				continue;
589			}
590
591			err = fCard->Extract(buf->Data(), &data, &sizeUsed);
592			if (err) {
593				buf->Recycle();
594				printf("OutVideo Extract error %s\n", strerror(err));
595				continue;
596			}
597
598			media_header* hdr = buf->Header();
599			hdr->type = B_MEDIA_ENCODED_VIDEO;
600			hdr->size_used = sizeUsed;
601			hdr->time_source = TimeSource()->ID();	// set time source id
602			//what should the start_time be?
603			hdr->start_time = TimeSource()->PerformanceTimeFor(system_time());
604
605			fLock.Lock();
606			if (SendBuffer(buf, fOutputEncVideo.source,
607					fOutputEncVideo.destination) != B_OK) {
608				TRACE("OutVideo: sending buffer failed\n");
609				buf->Recycle();
610			}
611			fLock.Unlock();
612		}
613
614	}
615}
616
617
618void
619FireWireDVNode::RefreshParameterWeb()
620{
621	TRACE("FireWireDVNode::RefreshParameterWeb enter\n");
622	fWeb = CreateParameterWeb();
623	SetParameterWeb(fWeb);
624	TRACE("FireWireDVNode::RefreshParameterWeb finished\n");
625}
626
627
628void
629FireWireDVNode::SetAboutInfo(BParameterGroup* about)
630{
631	//May need more useful infomation?
632	about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "FireWireDV media_addon info:", B_GENERIC);
633	about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "Version " VERSION, B_GENERIC);
634	about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "Revision " REVISION, B_GENERIC);
635	about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "Build " BUILD, B_GENERIC);
636
637	about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "", B_GENERIC);
638	about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "Driver info:", B_GENERIC);
639}
640
641
642BParameterWeb *
643FireWireDVNode::CreateParameterWeb()
644{
645	/* Set up the parameter web */
646	BParameterWeb* web = new BParameterWeb();
647
648	BString name;
649	name << Name();
650
651	BParameterGroup* main = web->MakeGroup(name.String());
652
653	if (!fCaptureActive) {
654		BParameterGroup* info = main->MakeGroup("Info");
655		info->MakeNullParameter(0, B_MEDIA_NO_TYPE, info->Name(), B_GENERIC);
656		BParameterGroup* about = main->MakeGroup("About");
657		about->MakeNullParameter(0, B_MEDIA_NO_TYPE, about->Name(), B_GENERIC);
658		SetAboutInfo(about);
659		info->MakeNullParameter(0, B_MEDIA_NO_TYPE, "Node is stopped", B_GENERIC);
660		info->MakeNullParameter(0, B_MEDIA_NO_TYPE, "or tuning failed.", B_GENERIC);
661		return web;
662	}
663
664	BParameterGroup* about = main->MakeGroup("About");
665	about->MakeNullParameter(0, B_MEDIA_NO_TYPE, about->Name(), B_GENERIC);
666	SetAboutInfo(about);
667
668	return web;
669}
670
671
672status_t
673FireWireDVNode::GetParameterValue(int32 id, bigtime_t* last_change,
674	void* value, size_t* size)
675{
676	TRACE("FireWireDVNode::GetParameterValue, id 0x%lx\n", id);
677	//do we need Parameter for firewire dv?
678	return B_OK;
679}
680
681
682void
683FireWireDVNode::SetParameterValue(int32 id, bigtime_t when, const void* value,
684	size_t size)
685{
686	TRACE("FireWireDVNode::SetParameterValue, id 0x%lx, size %ld, "
687		"value 0x%lx\n", id, size, *(const int32*)value);
688	//do we need parameter for firewire dv?
689	TRACE("FireWireDVNode::SetParameterValue finished\n");
690}
691
692