1 /*
2 * $Id: PlaybackNode.cc,v 1.1.1.1 2002/01/22 00:52:07 phil Exp $
3 * PortAudio Portable Real-Time Audio Library
4 * Latest Version at: http://www.portaudio.com
5 * BeOS Media Kit Implementation by Joshua Haberman
6 *
7 * Copyright (c) 2001 Joshua Haberman <joshua@haberman.com>
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining
10 * a copy of this software and associated documentation files
11 * (the "Software"), to deal in the Software without restriction,
12 * including without limitation the rights to use, copy, modify, merge,
13 * publish, distribute, sublicense, and/or sell copies of the Software,
14 * and to permit persons to whom the Software is furnished to do so,
15 * subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be
18 * included in all copies or substantial portions of the Software.
19 *
20 * Any person wishing to distribute modifications to the Software is
21 * requested to send the modifications to the original developer so that
22 * they can be incorporated into the canonical version.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
27 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
28 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
29 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 *
32 * ---
33 *
34 * Significant portions of this file are based on sample code from Be. The
35 * Be Sample Code Licence follows:
36 *
37 * Copyright 1991-1999, Be Incorporated.
38 * All rights reserved.
39 *
40 * Redistribution and use in source and binary forms, with or without
41 * modification, are permitted provided that the following conditions
42 * are met:
43 *
44 * 1. Redistributions of source code must retain the above copyright
45 * notice, this list of conditions, and the following disclaimer.
46 *
47 * 2. Redistributions in binary form must reproduce the above copyright
48 * notice, this list of conditions, and the following disclaimer in the
49 * documentation and/or other materials provided with the distribution.
50 *
51 * 3. The name of the author may not be used to endorse or promote products
52 * derived from this software without specific prior written permission.
53 *
54 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
55 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
56 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
57 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
58 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
59 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
60 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
61 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
62 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
63 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
64 */
65
66 #include <stdio.h>
67
68 #include <be/media/BufferGroup.h>
69 #include <be/media/Buffer.h>
70 #include <be/media/TimeSource.h>
71
72 #include "PlaybackNode.h"
73
74 #define PRINT(x) { printf x; fflush(stdout); }
75
76 #ifdef DEBUG
77 #define DBUG(x) PRINT(x)
78 #else
79 #define DBUG(x)
80 #endif
81
82
PaPlaybackNode(uint32 channels,float frame_rate,uint32 frames_per_buffer,PortAudioCallback * callback,void * user_data)83 PaPlaybackNode::PaPlaybackNode(uint32 channels, float frame_rate, uint32 frames_per_buffer,
84 PortAudioCallback* callback, void *user_data) :
85 BMediaNode("PortAudio input node"),
86 BBufferProducer(B_MEDIA_RAW_AUDIO),
87 BMediaEventLooper(),
88 mAborted(false),
89 mRunning(false),
90 mBufferGroup(NULL),
91 mDownstreamLatency(0),
92 mStartTime(0),
93 mCallback(callback),
94 mUserData(user_data),
95 mFramesPerBuffer(frames_per_buffer)
96 {
97 DBUG(("Constructor called.\n"));
98
99 mPreferredFormat.type = B_MEDIA_RAW_AUDIO;
100 mPreferredFormat.u.raw_audio.channel_count = channels;
101 mPreferredFormat.u.raw_audio.frame_rate = frame_rate;
102 mPreferredFormat.u.raw_audio.byte_order =
103 (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN;
104 mPreferredFormat.u.raw_audio.buffer_size =
105 media_raw_audio_format::wildcard.buffer_size;
106
107 mOutput.destination = media_destination::null;
108 mOutput.format = mPreferredFormat;
109
110 /* The amount of time it takes for this node to produce a buffer when
111 * asked. Essentially, it is how long the user's callback takes to run.
112 * We set this to be the length of the sound data each buffer of the
113 * requested size can hold. */
114 //mInternalLatency = (bigtime_t)(1000000 * frames_per_buffer / frame_rate);
115
116 /* ACK! it seems that the mixer (at least on my machine) demands that IT
117 * specify the buffer size, so for now I'll just make a generic guess here */
118 mInternalLatency = 1000000 / 20;
119 }
120
121
122
~PaPlaybackNode()123 PaPlaybackNode::~PaPlaybackNode()
124 {
125 DBUG(("Destructor called.\n"));
126 Quit(); /* Stop the BMediaEventLooper thread */
127 }
128
129
130 /*************************
131 *
132 * Local methods
133 *
134 */
135
IsRunning()136 bool PaPlaybackNode::IsRunning()
137 {
138 return mRunning;
139 }
140
141
GetStreamTime()142 PaTimestamp PaPlaybackNode::GetStreamTime()
143 {
144 BTimeSource *timeSource = TimeSource();
145 PaTimestamp time = (timeSource->Now() - mStartTime) *
146 mPreferredFormat.u.raw_audio.frame_rate / 1000000;
147 return time;
148 }
149
150
SetSampleFormat(PaSampleFormat inFormat,PaSampleFormat outFormat)151 void PaPlaybackNode::SetSampleFormat(PaSampleFormat inFormat,
152 PaSampleFormat outFormat)
153 {
154 uint32 beOutFormat;
155
156 switch(outFormat)
157 {
158 case paFloat32:
159 beOutFormat = media_raw_audio_format::B_AUDIO_FLOAT;
160 mOutputSampleWidth = 4;
161 break;
162
163 case paInt16:
164 beOutFormat = media_raw_audio_format::B_AUDIO_SHORT;
165 mOutputSampleWidth = 2;
166 break;
167
168 case paInt32:
169 beOutFormat = media_raw_audio_format::B_AUDIO_INT;
170 mOutputSampleWidth = 4;
171 break;
172
173 case paInt8:
174 beOutFormat = media_raw_audio_format::B_AUDIO_CHAR;
175 mOutputSampleWidth = 1;
176 break;
177
178 case paUInt8:
179 beOutFormat = media_raw_audio_format::B_AUDIO_UCHAR;
180 mOutputSampleWidth = 1;
181 break;
182
183 case paInt24:
184 case paPackedInt24:
185 case paCustomFormat:
186 DBUG(("Unsupported output format: %x\n", outFormat));
187 break;
188
189 default:
190 DBUG(("Unknown output format: %x\n", outFormat));
191 }
192
193 mPreferredFormat.u.raw_audio.format = beOutFormat;
194 mFramesPerBuffer * mPreferredFormat.u.raw_audio.channel_count * mOutputSampleWidth;
195 }
196
FillNextBuffer(bigtime_t time)197 BBuffer *PaPlaybackNode::FillNextBuffer(bigtime_t time)
198 {
199 /* Get a buffer from the buffer group */
200 BBuffer *buf = mBufferGroup->RequestBuffer(
201 mOutput.format.u.raw_audio.buffer_size, BufferDuration());
202 unsigned long frames = mOutput.format.u.raw_audio.buffer_size /
203 mOutputSampleWidth / mOutput.format.u.raw_audio.channel_count;
204 bigtime_t start_time;
205 int ret;
206
207 if( !buf )
208 {
209 DBUG(("Unable to allocate a buffer\n"));
210 return NULL;
211 }
212
213 start_time = mStartTime +
214 (bigtime_t)((double)mSamplesSent /
215 (double)mOutput.format.u.raw_audio.frame_rate /
216 (double)mOutput.format.u.raw_audio.channel_count *
217 1000000.0);
218
219 /* Now call the user callback to get the data */
220 ret = mCallback(NULL, /* Input buffer */
221 buf->Data(), /* Output buffer */
222 frames, /* Frames per buffer */
223 mSamplesSent / mOutput.format.u.raw_audio.channel_count, /* timestamp */
224 mUserData);
225
226 if( ret )
227 mAborted = true;
228
229 media_header *hdr = buf->Header();
230
231 hdr->type = B_MEDIA_RAW_AUDIO;
232 hdr->size_used = mOutput.format.u.raw_audio.buffer_size;
233 hdr->time_source = TimeSource()->ID();
234 hdr->start_time = start_time;
235
236 return buf;
237 }
238
239
240
241
242 /*************************
243 *
244 * BMediaNode methods
245 *
246 */
247
AddOn(int32 *) const248 BMediaAddOn *PaPlaybackNode::AddOn( int32 * ) const
249 {
250 DBUG(("AddOn() called.\n"));
251 return NULL; /* we don't provide service to outside applications */
252 }
253
254
HandleMessage(int32 message,const void * data,size_t size)255 status_t PaPlaybackNode::HandleMessage( int32 message, const void *data,
256 size_t size )
257 {
258 DBUG(("HandleMessage() called.\n"));
259 return B_ERROR; /* we don't define any custom messages */
260 }
261
262
263
264
265 /*************************
266 *
267 * BMediaEventLooper methods
268 *
269 */
270
NodeRegistered()271 void PaPlaybackNode::NodeRegistered()
272 {
273 DBUG(("NodeRegistered() called.\n"));
274
275 /* Start the BMediaEventLooper thread */
276 SetPriority(B_REAL_TIME_PRIORITY);
277 Run();
278
279 /* set up as much information about our output as we can */
280 mOutput.source.port = ControlPort();
281 mOutput.source.id = 0;
282 mOutput.node = Node();
283 ::strcpy(mOutput.name, "PortAudio Playback");
284 }
285
286
HandleEvent(const media_timed_event * event,bigtime_t lateness,bool realTimeEvent)287 void PaPlaybackNode::HandleEvent( const media_timed_event *event,
288 bigtime_t lateness, bool realTimeEvent )
289 {
290 // DBUG(("HandleEvent() called.\n"));
291 status_t err;
292
293 switch(event->type)
294 {
295 case BTimedEventQueue::B_START:
296 DBUG((" Handling a B_START event\n"));
297 if( RunState() != B_STARTED )
298 {
299 mStartTime = event->event_time + EventLatency();
300 mSamplesSent = 0;
301 mAborted = false;
302 mRunning = true;
303 media_timed_event firstEvent( mStartTime,
304 BTimedEventQueue::B_HANDLE_BUFFER );
305 EventQueue()->AddEvent( firstEvent );
306 }
307 break;
308
309 case BTimedEventQueue::B_STOP:
310 DBUG((" Handling a B_STOP event\n"));
311 mRunning = false;
312 EventQueue()->FlushEvents( 0, BTimedEventQueue::B_ALWAYS, true,
313 BTimedEventQueue::B_HANDLE_BUFFER );
314 break;
315
316 case BTimedEventQueue::B_HANDLE_BUFFER:
317 //DBUG((" Handling a B_HANDLE_BUFFER event\n"));
318
319 /* make sure we're started and connected */
320 if( RunState() != BMediaEventLooper::B_STARTED ||
321 mOutput.destination == media_destination::null )
322 break;
323
324 BBuffer *buffer = FillNextBuffer(event->event_time);
325
326 /* make sure we weren't aborted while this routine was running.
327 * this can happen in one of two ways: either the callback returned
328 * nonzero (in which case mAborted is set in FillNextBuffer() ) or
329 * the client called AbortStream */
330 if( mAborted )
331 {
332 if( buffer )
333 buffer->Recycle();
334 Stop(0, true);
335 break;
336 }
337
338 if( buffer )
339 {
340 err = SendBuffer(buffer, mOutput.destination);
341 if( err != B_OK )
342 buffer->Recycle();
343 }
344
345 mSamplesSent += mOutput.format.u.raw_audio.buffer_size / mOutputSampleWidth;
346
347 /* Now schedule the next buffer event, so we can send another
348 * buffer when this one runs out. We calculate when it should
349 * happen by calculating when the data we just sent will finish
350 * playing.
351 *
352 * NOTE, however, that the event will actually get generated
353 * earlier than we specify, to account for the latency it will
354 * take to produce the buffer. It uses the latency value we
355 * specified in SetEventLatency() to determine just how early
356 * to generate it. */
357
358 /* totalPerformanceTime includes the time represented by the buffer
359 * we just sent */
360 bigtime_t totalPerformanceTime = (bigtime_t)((double)mSamplesSent /
361 (double)mOutput.format.u.raw_audio.channel_count /
362 (double)mOutput.format.u.raw_audio.frame_rate * 1000000.0);
363
364 bigtime_t nextEventTime = mStartTime + totalPerformanceTime;
365
366 media_timed_event nextBufferEvent(nextEventTime,
367 BTimedEventQueue::B_HANDLE_BUFFER);
368 EventQueue()->AddEvent(nextBufferEvent);
369
370 break;
371
372 }
373 }
374
375
376
377
378 /*************************
379 *
380 * BBufferProducer methods
381 *
382 */
383
FormatSuggestionRequested(media_type type,int32,media_format * format)384 status_t PaPlaybackNode::FormatSuggestionRequested( media_type type,
385 int32 /*quality*/, media_format* format )
386 {
387 /* the caller wants to know this node's preferred format and provides
388 * a suggestion, asking if we support it */
389 DBUG(("FormatSuggestionRequested() called.\n"));
390
391 if(!format)
392 return B_BAD_VALUE;
393
394 *format = mPreferredFormat;
395
396 /* we only support raw audio (a wildcard is okay too) */
397 if ( type == B_MEDIA_UNKNOWN_TYPE || type == B_MEDIA_RAW_AUDIO )
398 return B_OK;
399 else
400 return B_MEDIA_BAD_FORMAT;
401 }
402
403
FormatProposal(const media_source & output,media_format * format)404 status_t PaPlaybackNode::FormatProposal( const media_source& output,
405 media_format* format )
406 {
407 /* This is similar to FormatSuggestionRequested(), but it is actually part
408 * of the negotiation process. We're given the opportunity to specify any
409 * properties that are wildcards (ie. properties that the other node doesn't
410 * care one way or another about) */
411 DBUG(("FormatProposal() called.\n"));
412
413 /* Make sure this proposal really applies to our output */
414 if( output != mOutput.source )
415 return B_MEDIA_BAD_SOURCE;
416
417 /* We return two things: whether we support the proposed format, and our own
418 * preferred format */
419 *format = mPreferredFormat;
420
421 if( format->type == B_MEDIA_UNKNOWN_TYPE || format->type == B_MEDIA_RAW_AUDIO )
422 return B_OK;
423 else
424 return B_MEDIA_BAD_FORMAT;
425 }
426
427
FormatChangeRequested(const media_source & source,const media_destination & destination,media_format * io_format,int32 *)428 status_t PaPlaybackNode::FormatChangeRequested( const media_source& source,
429 const media_destination& destination, media_format* io_format, int32* )
430 {
431 /* we refuse to change formats, supporting only 1 */
432 DBUG(("FormatChangeRequested() called.\n"));
433
434 return B_ERROR;
435 }
436
437
GetNextOutput(int32 * cookie,media_output * out_output)438 status_t PaPlaybackNode::GetNextOutput( int32* cookie, media_output* out_output )
439 {
440 /* this is where we allow other to enumerate our outputs -- the cookie is
441 * an integer we can use to keep track of where we are in enumeration. */
442 DBUG(("GetNextOutput() called.\n"));
443
444 if( *cookie == 0 )
445 {
446 *out_output = mOutput;
447 *cookie = 1;
448 return B_OK;
449 }
450
451 return B_BAD_INDEX;
452 }
453
454
DisposeOutputCookie(int32 cookie)455 status_t PaPlaybackNode::DisposeOutputCookie( int32 cookie )
456 {
457 DBUG(("DisposeOutputCookie() called.\n"));
458 return B_OK;
459 }
460
461
LateNoticeReceived(const media_source & what,bigtime_t how_much,bigtime_t performance_time)462 void PaPlaybackNode::LateNoticeReceived( const media_source& what,
463 bigtime_t how_much, bigtime_t performance_time )
464 {
465 /* This function is called as notification that a buffer we sent wasn't
466 * received by the time we stamped it with -- it got there late. Basically,
467 * it means we underestimated our own latency, so we should increase it */
468 DBUG(("LateNoticeReceived() called.\n"));
469
470 if( what != mOutput.source )
471 return;
472
473 if( RunMode() == B_INCREASE_LATENCY )
474 {
475 mInternalLatency += how_much;
476 SetEventLatency( mDownstreamLatency + mInternalLatency );
477 DBUG(("Increasing latency to %Ld\n", mDownstreamLatency + mInternalLatency));
478 }
479 else
480 DBUG(("I don't know what to do with this notice!"));
481 }
482
483
EnableOutput(const media_source & what,bool enabled,int32 *)484 void PaPlaybackNode::EnableOutput( const media_source& what, bool enabled,
485 int32* )
486 {
487 DBUG(("EnableOutput() called.\n"));
488 /* stub -- we don't support this yet */
489 }
490
491
PrepareToConnect(const media_source & what,const media_destination & where,media_format * format,media_source * out_source,char * out_name)492 status_t PaPlaybackNode::PrepareToConnect( const media_source& what,
493 const media_destination& where, media_format* format,
494 media_source* out_source, char* out_name )
495 {
496 /* the final stage of format negotiations. here we _must_ make specific any
497 * remaining wildcards */
498 DBUG(("PrepareToConnect() called.\n"));
499
500 /* make sure this really refers to our source */
501 if( what != mOutput.source )
502 return B_MEDIA_BAD_SOURCE;
503
504 /* make sure we're not already connected */
505 if( mOutput.destination != media_destination::null )
506 return B_MEDIA_ALREADY_CONNECTED;
507
508 if( format->type != B_MEDIA_RAW_AUDIO )
509 return B_MEDIA_BAD_FORMAT;
510
511 if( format->u.raw_audio.format != mPreferredFormat.u.raw_audio.format )
512 return B_MEDIA_BAD_FORMAT;
513
514 if( format->u.raw_audio.buffer_size ==
515 media_raw_audio_format::wildcard.buffer_size )
516 {
517 DBUG(("We were left to decide buffer size: choosing 2048"));
518 format->u.raw_audio.buffer_size = 2048;
519 }
520 else
521 DBUG(("Using consumer specified buffer size of %lu.\n",
522 format->u.raw_audio.buffer_size));
523
524 /* Reserve the connection, return the information */
525 mOutput.destination = where;
526 mOutput.format = *format;
527 *out_source = mOutput.source;
528 strncpy( out_name, mOutput.name, B_MEDIA_NAME_LENGTH );
529
530 return B_OK;
531 }
532
533
Connect(status_t error,const media_source & source,const media_destination & destination,const media_format & format,char * io_name)534 void PaPlaybackNode::Connect(status_t error, const media_source& source,
535 const media_destination& destination, const media_format& format, char* io_name)
536 {
537 DBUG(("Connect() called.\n"));
538
539