From 9c0e19a3be2288db79e2502e5fa450c3e20a668d Mon Sep 17 00:00:00 2001 From: Guenter Geiger Date: Fri, 9 May 2003 16:04:00 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r610, which included commits to RCS files with non-trunk default branches. svn path=/trunk/; revision=611 --- pd/portaudio/pa_beos/PlaybackNode.cc | 538 +++++++++++++++++++++++++++++++++++ 1 file changed, 538 insertions(+) create mode 100644 pd/portaudio/pa_beos/PlaybackNode.cc (limited to 'pd/portaudio/pa_beos/PlaybackNode.cc') diff --git a/pd/portaudio/pa_beos/PlaybackNode.cc b/pd/portaudio/pa_beos/PlaybackNode.cc new file mode 100644 index 00000000..41cbae34 --- /dev/null +++ b/pd/portaudio/pa_beos/PlaybackNode.cc @@ -0,0 +1,538 @@ +/* + * $Id: PlaybackNode.cc,v 1.1.1.1 2003-05-09 16:03:53 ggeiger Exp $ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * BeOS Media Kit Implementation by Joshua Haberman + * + * Copyright (c) 2001 Joshua Haberman + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * --- + * + * Significant portions of this file are based on sample code from Be. The + * Be Sample Code Licence follows: + * + * Copyright 1991-1999, Be Incorporated. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +#include "PlaybackNode.h" + +#define PRINT(x) { printf x; fflush(stdout); } + +#ifdef DEBUG +#define DBUG(x) PRINT(x) +#else +#define DBUG(x) +#endif + + +PaPlaybackNode::PaPlaybackNode(uint32 channels, float frame_rate, uint32 frames_per_buffer, + PortAudioCallback* callback, void *user_data) : + BMediaNode("PortAudio input node"), + BBufferProducer(B_MEDIA_RAW_AUDIO), + BMediaEventLooper(), + mAborted(false), + mRunning(false), + mBufferGroup(NULL), + mDownstreamLatency(0), + mStartTime(0), + mCallback(callback), + mUserData(user_data), + mFramesPerBuffer(frames_per_buffer) +{ + DBUG(("Constructor called.\n")); + + mPreferredFormat.type = B_MEDIA_RAW_AUDIO; + mPreferredFormat.u.raw_audio.channel_count = channels; + mPreferredFormat.u.raw_audio.frame_rate = frame_rate; + mPreferredFormat.u.raw_audio.byte_order = + (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + mPreferredFormat.u.raw_audio.buffer_size = + media_raw_audio_format::wildcard.buffer_size; + + mOutput.destination = media_destination::null; + mOutput.format = mPreferredFormat; + + /* The amount of time it takes for this node to produce a buffer when + * asked. Essentially, it is how long the user's callback takes to run. + * We set this to be the length of the sound data each buffer of the + * requested size can hold. */ + //mInternalLatency = (bigtime_t)(1000000 * frames_per_buffer / frame_rate); + + /* ACK! it seems that the mixer (at least on my machine) demands that IT + * specify the buffer size, so for now I'll just make a generic guess here */ + mInternalLatency = 1000000 / 20; +} + + + +PaPlaybackNode::~PaPlaybackNode() +{ + DBUG(("Destructor called.\n")); + Quit(); /* Stop the BMediaEventLooper thread */ +} + + +/************************* + * + * Local methods + * + */ + +bool PaPlaybackNode::IsRunning() +{ + return mRunning; +} + + +PaTimestamp PaPlaybackNode::GetStreamTime() +{ + BTimeSource *timeSource = TimeSource(); + PaTimestamp time = (timeSource->Now() - mStartTime) * + mPreferredFormat.u.raw_audio.frame_rate / 1000000; + return time; +} + + +void PaPlaybackNode::SetSampleFormat(PaSampleFormat inFormat, + PaSampleFormat outFormat) +{ + uint32 beOutFormat; + + switch(outFormat) + { + case paFloat32: + beOutFormat = media_raw_audio_format::B_AUDIO_FLOAT; + mOutputSampleWidth = 4; + break; + + case paInt16: + beOutFormat = media_raw_audio_format::B_AUDIO_SHORT; + mOutputSampleWidth = 2; + break; + + case paInt32: + beOutFormat = media_raw_audio_format::B_AUDIO_INT; + mOutputSampleWidth = 4; + break; + + case paInt8: + beOutFormat = media_raw_audio_format::B_AUDIO_CHAR; + mOutputSampleWidth = 1; + break; + + case paUInt8: + beOutFormat = media_raw_audio_format::B_AUDIO_UCHAR; + mOutputSampleWidth = 1; + break; + + case paInt24: + case paPackedInt24: + case paCustomFormat: + DBUG(("Unsupported output format: %x\n", outFormat)); + break; + + default: + DBUG(("Unknown output format: %x\n", outFormat)); + } + + mPreferredFormat.u.raw_audio.format = beOutFormat; + mFramesPerBuffer * mPreferredFormat.u.raw_audio.channel_count * mOutputSampleWidth; +} + +BBuffer *PaPlaybackNode::FillNextBuffer(bigtime_t time) +{ + /* Get a buffer from the buffer group */ + BBuffer *buf = mBufferGroup->RequestBuffer( + mOutput.format.u.raw_audio.buffer_size, BufferDuration()); + unsigned long frames = mOutput.format.u.raw_audio.buffer_size / + mOutputSampleWidth / mOutput.format.u.raw_audio.channel_count; + bigtime_t start_time; + int ret; + + if( !buf ) + { + DBUG(("Unable to allocate a buffer\n")); + return NULL; + } + + start_time = mStartTime + + (bigtime_t)((double)mSamplesSent / + (double)mOutput.format.u.raw_audio.frame_rate / + (double)mOutput.format.u.raw_audio.channel_count * + 1000000.0); + + /* Now call the user callback to get the data */ + ret = mCallback(NULL, /* Input buffer */ + buf->Data(), /* Output buffer */ + frames, /* Frames per buffer */ + mSamplesSent / mOutput.format.u.raw_audio.channel_count, /* timestamp */ + mUserData); + + if( ret ) + mAborted = true; + + media_header *hdr = buf->Header(); + + hdr->type = B_MEDIA_RAW_AUDIO; + hdr->size_used = mOutput.format.u.raw_audio.buffer_size; + hdr->time_source = TimeSource()->ID(); + hdr->start_time = start_time; + + return buf; +} + + + + +/************************* + * + * BMediaNode methods + * + */ + +BMediaAddOn *PaPlaybackNode::AddOn( int32 * ) const +{ + DBUG(("AddOn() called.\n")); + return NULL; /* we don't provide service to outside applications */ +} + + +status_t PaPlaybackNode::HandleMessage( int32 message, const void *data, + size_t size ) +{ + DBUG(("HandleMessage() called.\n")); + return B_ERROR; /* we don't define any custom messages */ +} + + + + +/************************* + * + * BMediaEventLooper methods + * + */ + +void PaPlaybackNode::NodeRegistered() +{ + DBUG(("NodeRegistered() called.\n")); + + /* Start the BMediaEventLooper thread */ + SetPriority(B_REAL_TIME_PRIORITY); + Run(); + + /* set up as much information about our output as we can */ + mOutput.source.port = ControlPort(); + mOutput.source.id = 0; + mOutput.node = Node(); + ::strcpy(mOutput.name, "PortAudio Playback"); +} + + +void PaPlaybackNode::HandleEvent( const media_timed_event *event, + bigtime_t lateness, bool realTimeEvent ) +{ + // DBUG(("HandleEvent() called.\n")); + status_t err; + + switch(event->type) + { + case BTimedEventQueue::B_START: + DBUG((" Handling a B_START event\n")); + if( RunState() != B_STARTED ) + { + mStartTime = event->event_time + EventLatency(); + mSamplesSent = 0; + mAborted = false; + mRunning = true; + media_timed_event firstEvent( mStartTime, + BTimedEventQueue::B_HANDLE_BUFFER ); + EventQueue()->AddEvent( firstEvent ); + } + break; + + case BTimedEventQueue::B_STOP: + DBUG((" Handling a B_STOP event\n")); + mRunning = false; + EventQueue()->FlushEvents( 0, BTimedEventQueue::B_ALWAYS, true, + BTimedEventQueue::B_HANDLE_BUFFER ); + break; + + case BTimedEventQueue::B_HANDLE_BUFFER: + //DBUG((" Handling a B_HANDLE_BUFFER event\n")); + + /* make sure we're started and connected */ + if( RunState() != BMediaEventLooper::B_STARTED || + mOutput.destination == media_destination::null ) + break; + + BBuffer *buffer = FillNextBuffer(event->event_time); + + /* make sure we weren't aborted while this routine was running. + * this can happen in one of two ways: either the callback returned + * nonzero (in which case mAborted is set in FillNextBuffer() ) or + * the client called AbortStream */ + if( mAborted ) + { + if( buffer ) + buffer->Recycle(); + Stop(0, true); + break; + } + + if( buffer ) + { + err = SendBuffer(buffer, mOutput.destination); + if( err != B_OK ) + buffer->Recycle(); + } + + mSamplesSent += mOutput.format.u.raw_audio.buffer_size / mOutputSampleWidth; + + /* Now schedule the next buffer event, so we can send another + * buffer when this one runs out. We calculate when it should + * happen by calculating when the data we just sent will finish + * playing. + * + * NOTE, however, that the event will actually get generated + * earlier than we specify, to account for the latency it will + * take to produce the buffer. It uses the latency value we + * specified in SetEventLatency() to determine just how early + * to generate it. */ + + /* totalPerformanceTime includes the time represented by the buffer + * we just sent */ + bigtime_t totalPerformanceTime = (bigtime_t)((double)mSamplesSent / + (double)mOutput.format.u.raw_audio.channel_count / + (double)mOutput.format.u.raw_audio.frame_rate * 1000000.0); + + bigtime_t nextEventTime = mStartTime + totalPerformanceTime; + + media_timed_event nextBufferEvent(nextEventTime, + BTimedEventQueue::B_HANDLE_BUFFER); + EventQueue()->AddEvent(nextBufferEvent); + + break; + + } +} + + + + +/************************* + * + * BBufferProducer methods + * + */ + +status_t PaPlaybackNode::FormatSuggestionRequested( media_type type, + int32 /*quality*/, media_format* format ) +{ + /* the caller wants to know this node's preferred format and provides + * a suggestion, asking if we support it */ + DBUG(("FormatSuggestionRequested() called.\n")); + + if(!format) + return B_BAD_VALUE; + + *format = mPreferredFormat; + + /* we only support raw audio (a wildcard is okay too) */ + if ( type == B_MEDIA_UNKNOWN_TYPE || type == B_MEDIA_RAW_AUDIO ) + return B_OK; + else + return B_MEDIA_BAD_FORMAT; +} + + +status_t PaPlaybackNode::FormatProposal( const media_source& output, + media_format* format ) +{ + /* This is similar to FormatSuggestionRequested(), but it is actually part + * of the negotiation process. We're given the opportunity to specify any + * properties that are wildcards (ie. properties that the other node doesn't + * care one way or another about) */ + DBUG(("FormatProposal() called.\n")); + + /* Make sure this proposal really applies to our output */ + if( output != mOutput.source ) + return B_MEDIA_BAD_SOURCE; + + /* We return two things: whether we support the proposed format, and our own + * preferred format */ + *format = mPreferredFormat; + + if( format->type == B_MEDIA_UNKNOWN_TYPE || format->type == B_MEDIA_RAW_AUDIO ) + return B_OK; + else + return B_MEDIA_BAD_FORMAT; +} + + +status_t PaPlaybackNode::FormatChangeRequested( const media_source& source, + const media_destination& destination, media_format* io_format, int32* ) +{ + /* we refuse to change formats, supporting only 1 */ + DBUG(("FormatChangeRequested() called.\n")); + + return B_ERROR; +} + + +status_t PaPlaybackNode::GetNextOutput( int32* cookie, media_output* out_output ) +{ + /* this is where we allow other to enumerate our outputs -- the cookie is + * an integer we can use to keep track of where we are in enumeration. */ + DBUG(("GetNextOutput() called.\n")); + + if( *cookie == 0 ) + { + *out_output = mOutput; + *cookie = 1; + return B_OK; + } + + return B_BAD_INDEX; +} + + +status_t PaPlaybackNode::DisposeOutputCookie( int32 cookie ) +{ + DBUG(("DisposeOutputCookie() called.\n")); + return B_OK; +} + + +void PaPlaybackNode::LateNoticeReceived( const media_source& what, + bigtime_t how_much, bigtime_t performance_time ) +{ + /* This function is called as notification that a buffer we sent wasn't + * received by the time we stamped it with -- it got there late. Basically, + * it means we underestimated our own latency, so we should increase it */ + DBUG(("LateNoticeReceived() called.\n")); + + if( what != mOutput.source ) + return; + + if( RunMode() == B_INCREASE_LATENCY ) + { + mInternalLatency += how_much; + SetEventLatency( mDownstreamLatency + mInternalLatency ); + DBUG(("Increasing latency to %Ld\n", mDownstreamLatency + mInternalLatency)); + } + else + DBUG(("I don't know what to do with this notice!")); +} + + +void PaPlaybackNode::EnableOutput( const media_source& what, bool enabled, + int32* ) +{ + DBUG(("EnableOutput() called.\n")); + /* stub -- we don't support this yet */ +} + + +status_t PaPlaybackNode::PrepareToConnect( const media_source& what, + const media_destination& where, media_format* format, + media_source* out_source, char* out_name ) +{ + /* the final stage of format negotiations. here we _must_ make specific any + * remaining wildcards */ + DBUG(("PrepareToConnect() called.\n")); + + /* make sure this really refers to our source */ + if( what != mOutput.source ) + return B_MEDIA_BAD_SOURCE; + + /* make sure we're not already connected */ + if( mOutput.destination != media_destination::null ) + return B_MEDIA_ALREADY_CONNECTED; + + if( format->type != B_MEDIA_RAW_AUDIO ) + return B_MEDIA_BAD_FORMAT; + + if( format->u.raw_audio.format != mPreferredFormat.u.raw_audio.format ) + return B_MEDIA_BAD_FORMAT; + + if( format->u.raw_audio.buffer_size == + media_raw_audio_format::wildcard.buffer_size ) + { + DBUG(("We were left to decide buffer size: choosing 2048")); + format->u.raw_audio.buffer_size = 2048; + } + else + DBUG(("Using consumer specified buffer size of %lu.\n", + format->u.raw_audio.buffer_size)); + + /* Reserve the connection, return the information */ + mOutput.destination = where; + mOutput.format = *format; + *out_source = mOutput.source; + strncpy( out_name, mOutput.name, B_MEDIA_NAME_LENGTH ); + + return B_OK; +} + + +void PaPlaybackNode::Connect(status_t error, const media_source& source, + const media_destination& destination, const media_format& format, char* io_name) +{ + DBUG(("Connect() called.\n")); + -- cgit v1.2.1