1// MediaWriter.cpp
2//
3// Andrew Bachmann, 2002
4//
5// A MediaWriter is a node that
6// implements FileInterface and BBufferConsumer.
7// It consumes on input, which is a multistream,
8// and writes the stream to a file.
9//
10// see also MediaWriterAddOn.cpp
11
12#include <MediaDefs.h>
13#include <MediaNode.h>
14#include <MediaAddOn.h>
15#include <BufferConsumer.h>
16#include <FileInterface.h>
17#include <Controllable.h>
18#include <MediaEventLooper.h>
19#include <File.h>
20#include <Errors.h>
21#include <Entry.h>
22#include <BufferGroup.h>
23#include <TimeSource.h>
24#include <Buffer.h>
25#include <ParameterWeb.h>
26#include <MediaRoster.h>
27#include <limits.h>
28
29#include "../AbstractFileInterfaceNode.h"
30#include "MediaWriter.h"
31#include "../misc.h"
32
33#include <stdio.h>
34#include <string.h>
35
36// -------------------------------------------------------- //
37// ctor/dtor
38// -------------------------------------------------------- //
39
40MediaWriter::~MediaWriter(void)
41{
42	fprintf(stderr,"MediaWriter::~MediaWriter\n");
43	if (fBufferGroup != 0) {
44		BBufferGroup * group = fBufferGroup;
45		fBufferGroup = 0;
46		delete group;
47	}
48}
49
50MediaWriter::MediaWriter(
51				size_t defaultChunkSize,
52				float defaultBitRate,
53				const flavor_info * info,
54				BMessage * config,
55				BMediaAddOn * addOn)
56	: BMediaNode("MediaWriter"),
57	  BBufferConsumer(B_MEDIA_MULTISTREAM),
58	  AbstractFileInterfaceNode(defaultChunkSize,defaultBitRate,info,config,addOn)
59{
60	fprintf(stderr,"MediaWriter::MediaWriter\n");
61	// null some fields
62	fBufferGroup = 0;
63	// don't overwrite available space, and be sure to terminate
64	strncpy(input.name,"MediaWriter Input",B_MEDIA_NAME_LENGTH-1);
65	input.name[B_MEDIA_NAME_LENGTH-1] = '\0';
66	// initialize the input
67	input.node = media_node::null;               // until registration
68	input.source = media_source::null;
69	input.destination = media_destination::null; // until registration
70	GetFormat(&input.format);
71}
72
73// -------------------------------------------------------- //
74// implementation of BMediaNode
75// -------------------------------------------------------- //
76
77void MediaWriter::Preroll(void)
78{
79	fprintf(stderr,"MediaWriter::Preroll\n");
80	// XXX:Performance opportunity
81	BMediaNode::Preroll();
82}
83
84status_t MediaWriter::HandleMessage(
85				int32 message,
86				const void * data,
87				size_t size)
88{
89	fprintf(stderr,"MediaWriter::HandleMessage\n");
90	status_t status = B_OK;
91	switch (message) {
92		// no special messages for now
93		default:
94			status = BBufferConsumer::HandleMessage(message,data,size);
95			if (status == B_OK) {
96				break;
97			}
98			status = AbstractFileInterfaceNode::HandleMessage(message,data,size);
99			break;
100	}
101	return status;
102}
103
104void MediaWriter::NodeRegistered(void)
105{
106	fprintf(stderr,"MediaWriter::NodeRegistered\n");
107
108	// now we can do this
109	input.node = Node();
110	input.destination.id = 0;
111	input.destination.port = input.node.port; // same as ControlPort()
112
113	// creates the parameter web and starts the looper thread
114	AbstractFileInterfaceNode::NodeRegistered();
115}
116
117// -------------------------------------------------------- //
118// implementation of BFileInterface
119// -------------------------------------------------------- //
120
121status_t MediaWriter::SetRef(
122				const entry_ref & file,
123				bool create,
124				bigtime_t * out_time)
125{
126	fprintf(stderr,"MediaWriter::SetRef\n");
127	status_t status;
128	status = AbstractFileInterfaceNode::SetRef(file,B_WRITE_ONLY,create,out_time);
129	if (status != B_OK) {
130		fprintf(stderr,"AbstractFileInterfaceNode::SetRef returned an error\n");
131		return status;
132	}
133	if (input.source == media_source::null) {
134		// reset the format, and set the requirements imposed by this file
135		GetFormat(&input.format);
136		AddRequirements(&input.format);
137		return B_OK;
138	}
139	// if we are connected we may have to re-negotiate the connection
140	media_format format;
141	GetFormat(&format);
142	AddRequirements(&format);
143	if (format_is_acceptible(input.format,format)) {
144		fprintf(stderr,"  compatible format = no re-negotiation necessary\n");
145		return B_OK;
146	}
147	// first try the easy way : SORRY DEPRECATED into private :-(
148//	int32 change_tag = NewChangeTag();
149//	status = this->BBufferConsumer::RequestFormatChange(input.source,input.destination,&format,&change_tag);
150//	if (status == B_OK) {
151//		fprintf(stderr,"  format change successful\n");
152//		return B_OK;
153//	}
154	// okay, the hard way requires we get the MediaRoster
155	BMediaRoster * roster = BMediaRoster::Roster(&status);
156	if (roster == 0) {
157		return B_MEDIA_SYSTEM_FAILURE;
158	}
159	if (status != B_OK) {
160		return status;
161	}
162	// before disconnect one should always stop the nodes (bebook says)
163	// requires run_state cast since the return type on RunState() is
164	// wrong [int32]
165	run_state destinationRunState = run_state(RunState());
166	if (destinationRunState == BMediaEventLooper::B_STARTED) {
167		Stop(0,true); // stop us right now
168	}
169	// should also stop the source if it is running, but how?
170/*	BMediaNode sourceNode = ??
171	run_state sourceRunState = sourceNode->??;
172	status = sourceNode->StopNode(??,0,true);
173	if (status != B_OK) {
174		return status;
175	}  */
176	// we should disconnect right now
177	media_source inputSource = input.source;
178	status = roster->Disconnect(input.source.id,input.source,
179							    input.destination.id,input.destination);
180	if (status != B_OK) {
181		return status;
182	}
183	// if that went okay, we'll try reconnecting
184	media_output connectOutput;
185	media_input connectInput;
186	status = roster->Connect(inputSource,input.destination,
187							 &format,&connectOutput,&connectInput);
188	if (status != B_OK) {
189		return status;
190	}
191	// now restart if necessary
192	if (destinationRunState == BMediaEventLooper::B_STARTED) {
193		Start(0);
194	}
195	return status;
196}
197
198// -------------------------------------------------------- //
199// implemention of BBufferConsumer
200// -------------------------------------------------------- //
201
202// Check to make sure the format is okay, then remove
203// any wildcards corresponding to our requirements.
204status_t MediaWriter::AcceptFormat(
205				const media_destination & dest,
206				media_format * format)
207{
208	fprintf(stderr,"MediaWriter::AcceptFormat\n");
209	if (format == 0) {
210		fprintf(stderr,"<- B_BAD_VALUE\n");
211		return B_BAD_VALUE; // no crashing
212	}
213	if (input.destination != dest) {
214		fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION");
215		return B_MEDIA_BAD_DESTINATION; // we only have one input so that better be it
216	}
217/*	media_format * myFormat = GetFormat();
218	fprintf(stderr,"proposed format: ");
219	print_media_format(format);
220	fprintf(stderr,"\n");
221	fprintf(stderr,"my format: ");
222	print_media_format(myFormat);
223	fprintf(stderr,"\n");*/
224	// Be's format_is_compatible doesn't work.
225//	if (!format_is_compatible(*format,*myFormat)) {
226	media_format myFormat;
227	GetFormat(&myFormat);
228	if (!format_is_acceptible(*format,myFormat)) {
229		fprintf(stderr,"<- B_MEDIA_BAD_FORMAT\n");
230		return B_MEDIA_BAD_FORMAT;
231	}
232	AddRequirements(format);
233	return B_OK;
234}
235
236status_t MediaWriter::GetNextInput(
237				int32 * cookie,
238				media_input * out_input)
239{
240	fprintf(stderr,"MediaWriter::GetNextInput\n");
241	// let's not crash even if they are stupid
242	if (out_input == 0) {
243		// no place to write!
244		fprintf(stderr,"<- B_BAD_VALUE\n");
245		return B_BAD_VALUE;
246	}
247	if (cookie != 0) {
248		// it's valid but they already got our 1 input
249		if (*cookie != 0) {
250			fprintf(stderr,"<- B_ERROR (no more inputs)\n");
251			return B_ERROR;
252		}
253		// so next time they won't get the same input again
254		*cookie = 1;
255	}
256	*out_input = input;
257	return B_OK;
258}
259
260void MediaWriter::DisposeInputCookie(
261				int32 cookie)
262{
263	fprintf(stderr,"MediaWriter::DisposeInputCookie\n");
264	// nothing to do since our cookies are just integers
265	return; // B_OK;
266}
267
268void MediaWriter::BufferReceived(
269				BBuffer * buffer)
270{
271	fprintf(stderr,"MediaWriter::BufferReceived\n");
272	switch (buffer->Header()->type) {
273		case B_MEDIA_PARAMETERS:
274			{
275			status_t status = ApplyParameterData(buffer->Data(),buffer->SizeUsed());
276			if (status != B_OK) {
277				fprintf(stderr,"ApplyParameterData in MediaWriter::BufferReceived failed\n");
278			}
279			buffer->Recycle();
280			}
281			break;
282		case B_MEDIA_MULTISTREAM:
283			if (buffer->Flags() & BBuffer::B_SMALL_BUFFER) {
284				fprintf(stderr,"NOT IMPLEMENTED: B_SMALL_BUFFER in MediaWriter::BufferReceived\n");
285				// XXX: implement this part
286				buffer->Recycle();
287			} else {
288				media_timed_event event(buffer->Header()->start_time, BTimedEventQueue::B_HANDLE_BUFFER,
289										buffer, BTimedEventQueue::B_RECYCLE_BUFFER);
290				status_t status = EventQueue()->AddEvent(event);
291				if (status != B_OK) {
292					fprintf(stderr,"EventQueue()->AddEvent(event) in MediaWriter::BufferReceived failed\n");
293					buffer->Recycle();
294				}
295			}
296			break;
297		default:
298			fprintf(stderr,"unexpected buffer type in MediaWriter::BufferReceived\n");
299			buffer->Recycle();
300			break;
301	}
302}
303
304void MediaWriter::ProducerDataStatus(
305				const media_destination & for_whom,
306				int32 status,
307				bigtime_t at_performance_time)
308{
309	fprintf(stderr,"MediaWriter::ProducerDataStatus\n");
310	if (input.destination != for_whom) {
311		fprintf(stderr,"invalid destination received in MediaWriter::ProducerDataStatus\n");
312		return;
313	}
314	media_timed_event event(at_performance_time, BTimedEventQueue::B_DATA_STATUS,
315			&input, BTimedEventQueue::B_NO_CLEANUP, status, 0, NULL);
316	EventQueue()->AddEvent(event);
317}
318
319status_t MediaWriter::GetLatencyFor(
320				const media_destination & for_whom,
321				bigtime_t * out_latency,
322				media_node_id * out_timesource)
323{
324	fprintf(stderr,"MediaWriter::GetLatencyFor\n");
325	if ((out_latency == 0) || (out_timesource == 0)) {
326		fprintf(stderr,"<- B_BAD_VALUE\n");
327		return B_BAD_VALUE;
328	}
329	if (input.destination != for_whom) {
330		fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
331		return B_MEDIA_BAD_DESTINATION;
332	}
333	*out_latency = EventLatency();
334	*out_timesource = TimeSource()->ID();
335	return B_OK;
336}
337
338status_t MediaWriter::Connected(
339				const media_source & producer,	/* here's a good place to request buffer group usage */
340				const media_destination & where,
341				const media_format & with_format,
342				media_input * out_input)
343{
344	fprintf(stderr,"MediaWriter::Connected\n");
345	if (out_input == 0) {
346		fprintf(stderr,"<- B_BAD_VALUE\n");
347		return B_BAD_VALUE; // no crashing
348	}
349	if (input.destination != where) {
350		fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
351		return B_MEDIA_BAD_DESTINATION;
352	}
353
354	// clear any stale buffer groups
355	if (fBufferGroup != 0) {
356		BBufferGroup * group = fBufferGroup;
357		fBufferGroup = 0;
358		delete group;
359	}
360
361	// compute the latency or just guess
362	if (GetCurrentFile() != 0) {
363		bigtime_t start, end;
364		uint8 * data = new uint8[input.format.u.multistream.max_chunk_size]; // <- buffer group buffer size
365		ssize_t bytesWritten = 0;
366		{ // timed section
367			start = TimeSource()->RealTime();
368			bytesWritten = GetCurrentFile()->Write(data,input.format.u.multistream.max_chunk_size);
369			end = TimeSource()->RealTime();
370		}
371		delete[] data;
372		GetCurrentFile()->Seek(-bytesWritten,SEEK_CUR); // put it back where we found it
373
374		fInternalLatency = end - start;
375
376		fprintf(stderr,"  internal latency from disk write = %lld\n",fInternalLatency);
377	} else {
378		fInternalLatency = 500; // just guess
379		fprintf(stderr,"  internal latency guessed = %lld\n",fInternalLatency);
380	}
381
382	SetEventLatency(fInternalLatency);
383
384	// record the agreed upon values
385	input.source = producer;
386	input.format = with_format;
387	*out_input = input;
388	return B_OK;
389}
390
391void MediaWriter::Disconnected(
392				const media_source & producer,
393				const media_destination & where)
394{
395	fprintf(stderr,"MediaWriter::Disconnected\n");
396	if (input.destination != where) {
397		fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
398		return;
399	}
400	if (input.source != producer) {
401		fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
402		return;
403	}
404	input.source = media_source::null;
405	GetFormat(&input.format);
406	if (fBufferGroup != 0) {
407		BBufferGroup * group = fBufferGroup;
408		fBufferGroup = 0;
409		delete group;
410	}
411}
412
413	/* The notification comes from the upstream producer, so he's already cool with */
414	/* the format; you should not ask him about it in here. */
415status_t MediaWriter::FormatChanged(
416				const media_source & producer,
417				const media_destination & consumer,
418				int32 change_tag,
419				const media_format & format)
420{
421	fprintf(stderr,"MediaWriter::FormatChanged\n");
422	if (input.source != producer) {
423		return B_MEDIA_BAD_SOURCE;
424	}
425	if (input.destination != consumer) {
426		return B_MEDIA_BAD_DESTINATION;
427	}
428	// Since we don't really care about the format of the data
429	// we can just continue to treat things the same way.
430	input.format = format;
431	return B_OK;
432}
433
434	/* Given a performance time of some previous buffer, retrieve the remembered tag */
435	/* of the closest (previous or exact) performance time. Set *out_flags to 0; the */
436	/* idea being that flags can be added later, and the understood flags returned in */
437	/* *out_flags. */
438status_t MediaWriter::SeekTagRequested(
439				const media_destination & destination,
440				bigtime_t in_target_time,
441				uint32 in_flags,
442				media_seek_tag * out_seek_tag,
443				bigtime_t * out_tagged_time,
444				uint32 * out_flags)
445{
446	fprintf(stderr,"MediaWriter::SeekTagRequested\n");
447	return BBufferConsumer::SeekTagRequested(destination,in_target_time,in_flags,
448											out_seek_tag,out_tagged_time,out_flags);
449}
450
451// -------------------------------------------------------- //
452// implementation for BMediaEventLooper
453// -------------------------------------------------------- //
454
455// protected:
456
457// how should we handle late buffers?  drop them?
458// notify the producer?
459status_t MediaWriter::HandleBuffer(
460				const media_timed_event *event,
461				bigtime_t lateness,
462				bool realTimeEvent)
463{
464	fprintf(stderr,"MediaWriter::HandleBuffer\n");
465	BBuffer * buffer = const_cast<BBuffer*>((BBuffer*)event->pointer);
466	if (buffer == 0) {
467		fprintf(stderr,"<- B_BAD_VALUE\n");
468		return B_BAD_VALUE;
469	}
470	if (buffer->Header()->destination != input.destination.id) {
471		fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
472		return B_MEDIA_BAD_DESTINATION;
473	}
474	WriteFileBuffer(buffer);
475	buffer->Recycle();
476	return B_OK;
477}
478
479status_t MediaWriter::HandleDataStatus(
480						const media_timed_event *event,
481						bigtime_t lateness,
482						bool realTimeEvents)
483{
484	fprintf(stderr,"MediaWriter::HandleDataStatus");
485	// we have no where to send a data status to.
486	return B_OK;
487}
488
489
490// -------------------------------------------------------- //
491// MediaWriter specific functions
492// -------------------------------------------------------- //
493
494// static:
495
496void MediaWriter::GetFlavor(flavor_info * outInfo, int32 id)
497{
498	fprintf(stderr,"MediaWriter::GetFlavor\n");
499	if (outInfo == 0) {
500		return;
501	}
502	AbstractFileInterfaceNode::GetFlavor(outInfo,id);
503	strcpy(outInfo->name, "Media Writer");
504	strcpy(outInfo->info,
505		"The Haiku Media Writer consumes a multistream and writes a file.");
506	outInfo->kinds |= B_BUFFER_CONSUMER;
507	outInfo->in_format_count = 1; // 1 input
508	media_format * formats = new media_format[outInfo->in_format_count];
509	GetFormat(&formats[0]);
510	outInfo->in_formats = formats;
511	return;
512}
513
514void MediaWriter::GetFormat(media_format * outFormat)
515{
516	fprintf(stderr,"MediaWriter::GetFormat\n");
517	if (outFormat == 0) {
518		return;
519	}
520	AbstractFileInterfaceNode::GetFormat(outFormat);
521	return;
522}
523
524void MediaWriter::GetFileFormat(media_file_format * outFileFormat)
525{
526	fprintf(stderr,"MediaWriter::GetFileFormat\n");
527	if (outFileFormat == 0) {
528		return;
529	}
530	AbstractFileInterfaceNode::GetFileFormat(outFileFormat);
531	outFileFormat->capabilities |= media_file_format::B_WRITABLE;
532	return;
533}
534
535// protected:
536
537status_t MediaWriter::WriteFileBuffer(
538				BBuffer * buffer)
539{
540	fprintf(stderr,"MediaWriter::WriteFileBuffer\n");
541	if (GetCurrentFile() == 0) {
542		fprintf(stderr,"<- B_NO_INIT\n");
543		return B_NO_INIT;
544	}
545	fprintf(stderr,"  writing %" B_PRId32 " bytes at %lld\n",
546			buffer->SizeUsed(),GetCurrentFile()->Position());
547	ssize_t bytesWriten = GetCurrentFile()->Write(buffer->Data(),buffer->SizeUsed());
548	if (bytesWriten < 0) {
549		fprintf(stderr,"<- B_FILE_ERROR\n");
550		return B_FILE_ERROR; // some sort of file related error
551	}
552	// nothing more to say?
553	return B_OK;
554}
555
556// -------------------------------------------------------- //
557// stuffing
558// -------------------------------------------------------- //
559
560status_t MediaWriter::_Reserved_MediaWriter_0(void *) { return B_ERROR; }
561status_t MediaWriter::_Reserved_MediaWriter_1(void *) { return B_ERROR; }
562status_t MediaWriter::_Reserved_MediaWriter_2(void *) { return B_ERROR; }
563status_t MediaWriter::_Reserved_MediaWriter_3(void *) { return B_ERROR; }
564status_t MediaWriter::_Reserved_MediaWriter_4(void *) { return B_ERROR; }
565status_t MediaWriter::_Reserved_MediaWriter_5(void *) { return B_ERROR; }
566status_t MediaWriter::_Reserved_MediaWriter_6(void *) { return B_ERROR; }
567status_t MediaWriter::_Reserved_MediaWriter_7(void *) { return B_ERROR; }
568status_t MediaWriter::_Reserved_MediaWriter_8(void *) { return B_ERROR; }
569status_t MediaWriter::_Reserved_MediaWriter_9(void *) { return B_ERROR; }
570status_t MediaWriter::_Reserved_MediaWriter_10(void *) { return B_ERROR; }
571status_t MediaWriter::_Reserved_MediaWriter_11(void *) { return B_ERROR; }
572status_t MediaWriter::_Reserved_MediaWriter_12(void *) { return B_ERROR; }
573status_t MediaWriter::_Reserved_MediaWriter_13(void *) { return B_ERROR; }
574status_t MediaWriter::_Reserved_MediaWriter_14(void *) { return B_ERROR; }
575status_t MediaWriter::_Reserved_MediaWriter_15(void *) { return B_ERROR; }
576