xref: /haiku/src/add-ons/media/media-add-ons/writer/MediaWriter.cpp (revision cbe0a0c436162d78cc3f92a305b64918c839d079)
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 
40 MediaWriter::~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 
50 MediaWriter::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 
77 void MediaWriter::Preroll(void)
78 {
79 	fprintf(stderr,"MediaWriter::Preroll\n");
80 	// XXX:Performance opportunity
81 	BMediaNode::Preroll();
82 }
83 
84 status_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 
104 void 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 
121 status_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.
204 status_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 
236 status_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 
260 void 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 
268 void 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 
304 void 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 
319 status_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 
338 status_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 
391 void 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. */
415 status_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. */
438 status_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?
459 status_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 
479 status_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 
496 void 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 
514 void 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 
524 void 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 
537 status_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 
560 status_t MediaWriter::_Reserved_MediaWriter_0(void *) { return B_ERROR; }
561 status_t MediaWriter::_Reserved_MediaWriter_1(void *) { return B_ERROR; }
562 status_t MediaWriter::_Reserved_MediaWriter_2(void *) { return B_ERROR; }
563 status_t MediaWriter::_Reserved_MediaWriter_3(void *) { return B_ERROR; }
564 status_t MediaWriter::_Reserved_MediaWriter_4(void *) { return B_ERROR; }
565 status_t MediaWriter::_Reserved_MediaWriter_5(void *) { return B_ERROR; }
566 status_t MediaWriter::_Reserved_MediaWriter_6(void *) { return B_ERROR; }
567 status_t MediaWriter::_Reserved_MediaWriter_7(void *) { return B_ERROR; }
568 status_t MediaWriter::_Reserved_MediaWriter_8(void *) { return B_ERROR; }
569 status_t MediaWriter::_Reserved_MediaWriter_9(void *) { return B_ERROR; }
570 status_t MediaWriter::_Reserved_MediaWriter_10(void *) { return B_ERROR; }
571 status_t MediaWriter::_Reserved_MediaWriter_11(void *) { return B_ERROR; }
572 status_t MediaWriter::_Reserved_MediaWriter_12(void *) { return B_ERROR; }
573 status_t MediaWriter::_Reserved_MediaWriter_13(void *) { return B_ERROR; }
574 status_t MediaWriter::_Reserved_MediaWriter_14(void *) { return B_ERROR; }
575 status_t MediaWriter::_Reserved_MediaWriter_15(void *) { return B_ERROR; }
576