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