/* * beaudio.cxx * * Sound driver implementation. * * Portable Windows Library * * Copyright (c) 1993-2001 Equivalence Pty. Ltd. * * The contents of this file are subject to the Mozilla Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is Portable Windows Library. * * The Initial Developer of the Original Code is Equivalence Pty. Ltd. * * Portions are Copyright (C) 1993 Free Software Foundation, Inc. * All Rights Reserved. * * Contributor(s): * Yuri Kiryanov, ykiryanov at users.sourceforge.net, * Jac Goudsmit . * * $Log: beaudio.cxx,v $ * Revision 1.16 2004/10/26 18:08:54 ykiryanov * Added code for old Media Kit, to be backwards compatible with R5, and Zeta ifdef * * Revision 1.15 2004/06/16 01:55:10 ykiryanov * Added usage of lastReadCount - sound capture now works * * Revision 1.14 2004/05/30 04:48:45 ykiryanov * Stable version * * Revision 1.12 2004/05/14 05:26:57 ykiryanov * Fixed dynamic cast bug * * Revision 1.11 2004/04/18 00:32:26 ykiryanov * Fized compiler choking on . * * Revision 1.10 2004/04/02 03:29:07 ykiryanov * New improved code * * Revision 1.9 2002/02/09 00:52:01 robertj * Slight adjustment to API and documentation for volume functions. * * Revision 1.8 2002/02/07 20:57:21 dereks * add SetVolume and GetVolume methods to PSoundChannelBeOS * * Revision 1.7 2001/07/09 06:16:15 yurik * Jac Goudsmit's BeOS changes of July,6th. Cleaning up media subsystem etc. * * Revision 1.6 2000/12/16 13:08:56 rogerh * BeOS changes from Yuri Kiryanov * * Revision 1.5 2000/04/19 00:13:52 robertj * BeOS port changes. * * Revision 1.4 1999/09/21 00:56:29 robertj * Added more sound support for BeOS (thanks again Yuri!) * * Revision 1.3 1999/06/28 09:28:02 robertj * Portability issues, especially n BeOS (thanks Yuri!) * * Revision 1.2 1999/03/05 07:03:27 robertj * Some more BeOS port changes. * * Revision 1.1 1999/03/02 05:41:59 robertj * More BeOS changes * */ #include #include PCREATE_SOUND_PLUGIN(BeOS, PSoundChannelBeOS); /////////////// Debugging stuff /////////////// #define TL (7) #define PRINT(x) //do { printf(__FILE__ ":%d %s ", __LINE__, __FUNCTION__); printf x; printf("\n"); } while(0) #define STATUS(x) // PRINT((x "=%ld", (long)dwLastError)) #define PRINTCB(x) // PRINT(x) //#define SOUNDDETECT 1 define this for printed output of first pb/rec audio //#define FILEDUMP 1 define this for dumping audio to wav file // Macros and global vars for debugging #ifdef SOUNDDETECT #define DETECTVARS(buffer,numframes) short *detbuf=(short*)buffer; size_t detframes=numframes; #define DETECTSOUND() \ do { \ static bool silence=true; \ if (silence) \ { \ for (size_t i=0; i=255) \ { \ PRINT(("SOUND DETECTED at %p",detbuf)); \ for (size_t j=0; j>10)+32); \ printf(x,"."); \ free(x); \ } \ silence=false; \ break; \ } \ } \ } \ } while(0) #else #define DETECTVARS(buffer,numframes) #define DETECTSOUND() #endif #ifdef FILEDUMP #include "beaudio/AudioFileWriter.h" BAudioFileWriter *playwriter=NULL; BAudioFileWriter *recwriter=NULL; #endif //////////////////////////////////////////////////////////////////////////////// // PSound PSound::PSound(unsigned channels, unsigned samplesPerSecond, unsigned bitsPerSample, PINDEX bufferSize, const BYTE * buffer) { encoding = 0; SetFormat(channels, samplesPerSecond, bitsPerSample); if (buffer != NULL) { memcpy(GetPointer(bufferSize), buffer, bufferSize); } } PSound::PSound(const PFilePath & filename) { encoding = 0; // Set the default format SetFormat(1, 8000, 16); // The format is changed if the file is succesfully loaded. Load(filename); } PSound & PSound::operator=(const PBYTEArray & data) { PBYTEArray::operator=(data); return *this; } void PSound::SetFormat(unsigned channels, unsigned samplesPerSecond, unsigned bitsPerSample) { // NOTE: all constructors should call this to initialize // the local members, especially formatInfo. // Do NOT call the function with any parameter set to 0! sampleSize=bitsPerSample; sampleRate=samplesPerSecond; numChannels = channels; // We don't use the encoding member (although we could probably set it to 0=PCM) // Let the application know it shouldn't assume anything encoding = 1; // The formatInfo member to us is a media_format structure. BOOL setsize_formatInfo=formatInfo.SetSize(sizeof(media_format)); PAssert(setsize_formatInfo, "Unable to set size for sound info array"); // Initialize the media_format struct // The numbers of bits that we support here are 8, 16 or 32 bits (signed), // results for other sizes are not defined. media_format &format=*(media_format*)(const BYTE *)formatInfo; format.type = B_MEDIA_RAW_AUDIO; format.u.raw_audio = media_raw_audio_format::wildcard; format.u.raw_audio.frame_rate=(float)sampleRate; format.u.raw_audio.channel_count=numChannels; format.u.raw_audio.format=(sampleSize / 8) & 0xF; format.u.raw_audio.byte_order=B_MEDIA_HOST_ENDIAN; format.u.raw_audio.buffer_size=(channels * samplesPerSecond * (bitsPerSample/8))/10; // 1/10 sec buffer } BOOL PSound::Load(const PFilePath & filename) { // format is a reference to the formatInfo member which stores info // about the media format. This is needed for writing the data back // or for playing the sound. media_format &format=*(media_format *)(const BYTE *)formatInfo; // Create BEntry from file name BEntry entry(filename, true); if ((dwLastError=entry.InitCheck())!=B_OK) { STATUS("entry.InitCheck()"); return FALSE; } // Create entry_ref from BEntry entry_ref ref; if ((dwLastError=entry.GetRef(&ref))!=B_OK) { STATUS("entry.GetRef()"); return FALSE; } // Create BMediaFile for read access from the entry_ref BMediaFile file(&ref); if ((dwLastError=file.InitCheck())!=B_OK) { STATUS("file.InitCheck()"); return FALSE; } // Search for the first media track that can be decoded BMediaTrack *ptrack = NULL; for (int index=0; (indexInitCheck(); } else { dwLastError = B_ERROR; //todo: change error code } if (dwLastError==B_OK) { // Get media format; we're looking for a raw audio track. format.type = B_MEDIA_RAW_AUDIO; format.u.raw_audio = media_raw_audio_format::wildcard; dwLastError = ptrack->DecodedFormat(&format); if ((dwLastError==B_OK) && (format.type==B_MEDIA_RAW_AUDIO)) { break; // found a decodable track } } else { STATUS("TrackAt() failed, error"); } // if we found a track and arrived at this point, the track we found // was not decodable if (ptrack) { dwLastError=file.ReleaseTrack(ptrack); // destroys ptrack } } // if an error occurred during track scanning, leave now if (dwLastError!=B_OK) { return FALSE; } // Get a reference to the raw output format media_raw_audio_format &rawformat = format.u.raw_audio; // Fill in our fields from the format sampleSize = (rawformat.format & 0xF) * 8; numChannels = rawformat.channel_count; if (rawformat.frame_rate>0.0 && rawformat.frame_rate<=(float)0xFFFFFFFFU) { sampleRate = (unsigned)(rawformat.frame_rate); } else { // unknown or unrepresentable sample rate. // It's not really documented what we should do in this case but // it probably doesn't matter either... sampleRate = 0; } // Get the number of frames for the track and determine how much // memory we need to store the file's data // The multiplication might overflow for huge files but we don't // want to read them into memory anyway so I guess it's ok... int64 numframes = ptrack->CountFrames(); int64 framesize = numChannels * (sampleSize/8); int64 numbytes = numframes * framesize; // Set the size of the object's data area if (!SetSize(numbytes)) { PRINT(("Can't set size of sound to %Ld", numbytes)); dwLastError = B_ERROR; //todo replace by better error code return FALSE; // BMediaFile will destroy ptrack } // Read all frames into memory. NOTE: not thread safe! BYTE* dest = GetPointer(); // destination pointer int64 framecount = numframes; // number of frames left to read int64 framesread; // number of actual frames done while ((framecount!=0) && (dwLastError==B_OK)) { framesread = framecount; dwLastError = ptrack->ReadFrames(dest, &framesread); dest += framesread * framesize; framecount -= framesread; } // return true for success return (dwLastError==B_OK); // BMediaFile will destroy ptrack } BOOL PSound::Save(const PFilePath & filename) { // format is a reference to the formatInfo member which stores info // about the media format. This is needed for writing the data back // or for playing the sound. media_format &format=*(media_format *)(const BYTE *)formatInfo; // Get the file type from the file name's extension; if none, use wav PFilePathString filetype=filename.GetType(); // e.g. ".wav" if (filetype=="") { filetype="wav"; } else { filetype=filetype.Mid(1); // cut off the '.' } // Try to find the file format in BeOS's list of formats media_file_format mfi; int32 cookie=0; while ((dwLastError=get_next_file_format(&cookie, &mfi))==B_OK) { if (!strcasecmp(mfi.file_extension, (const char *)filetype)) { break; } } if (dwLastError!=B_OK) { // didn't find file format PRINT(("Couldn't find media_file_format for \"%s\"", (const char *)filetype)); return FALSE; } // Create BEntry from file name BEntry entry(filename, true); if ((dwLastError=entry.InitCheck())!=B_OK) { STATUS("entry.InitCheck()"); return FALSE; } // Create entry_ref from BEntry entry_ref ref; if ((dwLastError=entry.GetRef(&ref))!=B_OK) { STATUS("entry.GetRef()"); return FALSE; } // Create BMediaFile for write access from the entry_ref BMediaFile file(&ref, &mfi, B_MEDIA_FILE_REPLACE_MODE); if ((dwLastError=file.InitCheck())!=B_OK) { STATUS("file.InitCheck()"); return FALSE; } // Find an encoder. The input format is the format we have stored in // our formatInfo member. cookie=0; media_format outformat; media_codec_info mci,validmci,rawmci, *pmci; bool found_encoder = false; bool found_raw_encoder = false; while (get_next_encoder(&cookie, &mfi, &format, &outformat, &mci)==B_OK) { found_encoder=true; if (outformat.type==B_MEDIA_RAW_AUDIO) { rawmci=mci; found_raw_encoder=true; } else { validmci=mci; } } // Choose an encoder: // If a raw-output encoder was found, use it. // Else, use the last found encoded-output encoder, if any. // This method of choosing will make sure that most file formats // will get the most common encoding (PCM) whereas it's still possible // to choose another output format like MP3, if so dictated by the // file format. // BeOS is smart enough not to return an encoder that produces raw audio // for e.g. the MP3 file format, but it knows that there are many ways // to encode e.g. a WAV file and we don't want to put anything // unexpected into a WAV file, do we? BMediaTrack *ptrack = NULL; if (found_encoder) { if (found_raw_encoder) { PRINT(("Using raw encoder")); pmci=&rawmci; } else { // don't use mci instead of validmci, // it could be unreliable after the last call to get_next_encoder PRINT(("Using non-raw encoder")); pmci=&validmci; } // Create a BMediaTrack in the file using the selected encoder ptrack = file.CreateTrack(&format, pmci); if (ptrack) { dwLastError = ptrack->InitCheck(); } else { dwLastError = B_ERROR; //todo: change error code } } else { dwLastError=B_ERROR; //todo: change error code } if (dwLastError!=B_OK) { STATUS("Encoder not found or file.CreateTrack() error"); return FALSE; // BMediaFile will destroy ptrack } // We're only creating one track so commit the header now if ((dwLastError = file.CommitHeader())!=B_OK) { STATUS("file.CommitHeader()"); return FALSE; } // Determine how many frames we have to write // There is a small possibility of a divide by zero but this only // happens if the object is not properly initialized. PINDEX numbytes = GetSize(); int32 framesize = numChannels * (sampleSize/8); int32 numframes = numbytes / framesize; // divide by zero possibility ignored. if ((dwLastError=ptrack->WriteFrames((const BYTE *)*this, numframes))!=B_OK) { STATUS("ptrack->WriteFrames()"); return FALSE; // BMediaFile will destroy ptrack } return (file.CloseFile()==B_OK); // BMediaFile will destroy ptrack } BOOL PSound::Play() { PSoundChannelBeOS player(PSoundChannelBeOS::GetDefaultDevice(PSoundChannelBeOS::Player), PSoundChannelBeOS::Player, numChannels, sampleRate, sampleSize); if (!player.IsOpen()) { PRINT(("PSoundChannelBeOS constructor failed to open")); return FALSE; } return player.PlaySound(*this, TRUE); } BOOL PSound::PlayFile(const PFilePath & file, BOOL wait) { entry_ref ref; status_t err; // can't use dwLastError because this function is static // using pointers for these objects so that we don't have to // construct them here but can nevertheless use the if(ok)'s BEntry *pentry = NULL; { // Create BEntry from file name pentry = new BEntry(file, true); err = pentry->InitCheck(); } if (err==B_OK) { // Create entry_ref from BEntry err = pentry->GetRef(&ref); } if (err==B_OK) { // Play the sound. Return value is a handle or a negative value for errors // Errors in BeOS are always negative values err=play_sound(&ref, true, !wait, wait); if (err>=0) { err=B_OK; } } return (err==B_OK); } void PSound::Beep() { ::beep(); } //////////////////////////////////////////////////////////////////////////////// // CircularBuffer class Guard { private: sem_id mSem; public: Guard(sem_id sem) { acquire_sem(mSem=sem); } ~Guard() { release_sem(mSem); } }; /* This class represents a circular FIFO buffer. The buffer has a head and a tail that chase each other. The data is added to the buffer at the tail side by using Fill. The data from the buffer can be read starting at the head side using Drain. It is possible to use two threads to fill and drain the buffer but there should not be more than 2 threads doing draining and filling. Resetting (flushing) or destroying from a third thread is allowed; do make sure that any threads that operate on buffer data are stopped before destroying a buffer. Normally, filling and draining operations block the thread as short as possible (i.e. only when the other thread needs to update the head and tail pointers etc). If the filling thread tries to put data into a full or almost full buffer, it just returns after filling as much data as it can, and if the draining thread tries to get more data out than is in the buffer, it will simply return with the data that is there. In order to move all the data from an external buffer into an object of this class, the caller would have to call Fill repeatedly until all the data has been processed (similarly it would have to call Drain until it receives sufficient data). But if the application has nothing else to do in the mean time, this constitutes a Busy Waiting loop on either the filling or draining side of the FIFO buffer that slurps up as much CPU time as possible. To improve this behaviour, it's possible to specify a threshold value that is used to change the state to FullEnough and EmptyEnough. By using these states (instead of Full and Empty), one thread can block until the other thread has determined that there is enough data or enough room for data. */ class CircularBuffer { public: // Internal state for the buffer // Note the nifty bit patterns for comparing the current state // with a desired state typedef enum { // Headspace Tailspace Empty =1, // 0 size Filled =2, // 0=drainthreshold EmptyEnough =32, // t>=fillthreshold } State; protected: friend class ResamplingBuffer; // needed for one of their constructors BYTE *mBuffer; // the buffer PINDEX mSize; // size of the buffer in bytes volatile PINDEX mHead; // index where to start reading volatile PINDEX mTail; // index where to start writing volatile PINDEX mHeadRoom; // consecutive space from head to end-of-buffer or tail volatile PINDEX mTailRoom; // consecutive space from tail to end-of-buffer or head volatile PINDEX mSizeUsed; // total bytes in use volatile PINDEX mFillThreshold; // see above volatile PINDEX mDrainThreshold; // see above volatile State mState; // current state of the buffer sem_id mSemInUse; // used to guard data integrity sem_id mSemStateChange; // used to wait for state changes protected: // Check if the state changed. Private because it's not guarded by semaphore void UpdateState(void) { // Determine current state State newstate; if (mSizeUsed==mSize) { PRINTCB(("State is FULL")); newstate=Full; } else if (mSizeUsed==0) { PRINTCB(("State is EMPTY")); newstate=Empty; } else { PRINTCB(("State is FILLED")); newstate=Filled; } // Check thresholds if (mSize-mSizeUsed>=mFillThreshold) { PRINTCB(("...and EMPTYENOUGH")); newstate=(State)(newstate | EmptyEnough); } if (mSizeUsed>=mDrainThreshold) { PRINTCB(("...and FULLENOUGH")); newstate=(State)(newstate | FullEnough); } // Check if the state changed if (newstate!=mState) { PRINTCB(("Updating state from %X to %X", mState, newstate)); // Set the new state mState=newstate; // Signal state change release_sem(mSemStateChange); } } virtual size_t Write( BYTE *dest, // destination const BYTE **extbuf, // source, to be updated size_t size, // space in destination size_t *extsize) // data in source, to be updated { // This function is called to put data into the buffer size_t todo=MIN(size, *extsize); memcpy(dest, *extbuf, todo); *extbuf +=todo; // The external pointer moves forward... *extsize -=todo; // ... so the remaining size decreases return todo; } virtual size_t Read( BYTE **extbuf, // destination, to be updated const BYTE *src, // source size_t *extsize, // space in destination, to be updated size_t size) // data in source { // This function is called to read data out of the buffer size_t todo=MIN(size, *extsize); memcpy(*extbuf, src, todo); *extbuf +=todo; // The external pointer moves forward... *extsize -=todo; // ... so the remaining size decreases return todo; } public: // Reset buffer so that it can be filled again void Reset(void) { Guard _(mSemInUse); // guard data integrity mHead=mHeadRoom=mTail=mSizeUsed=0; mTailRoom=GetSize(); mState=(State)(Flushed|Empty|EmptyEnough); } // Constructor CircularBuffer( PINDEX size, PINDEX fillthreshold = 0, PINDEX drainthreshold = 0) : mFillThreshold(fillthreshold), mDrainThreshold(drainthreshold), mState(Empty) { PAssert(size!=0, "Attempting to create a buffer with size 0"); mSemInUse=create_sem(1, "mSemInUse"); mSemStateChange=create_sem(0, "mSemStateChange"); PAssert(mSemInUse>=0 && mSemStateChange>=0, "Unable to create semaphores"); mBuffer=new BYTE[(mSize=size)]; Reset(); } // Destructor virtual ~CircularBuffer() { // make sure the in-use semaphore is free and stays free while (acquire_sem_etc(mSemInUse,1,B_RELATIVE_TIMEOUT,0)==B_WOULD_BLOCK) { // nothing to do, just busy-wait } delete_sem(mSemInUse); delete_sem(mSemStateChange); Reset(); if(mBuffer) delete[] mBuffer; } // Check if buffer is empty bool IsEmpty() { return (mState==Empty); } // Check if buffer is full bool IsFull() { return (mState==Full); } // Get the size of the buffer PINDEX GetSize(void) { return mSize; } // Wait asynchronously for a buffer state or one of a number of states void WaitForState(State state) { PRINTCB(("Waiting for state %X, current state=%X this=%p", state, mState, this)); // reset the Flushed bit so it only stops the loop if the buffer // is flushed DURING an operation { Guard _(mSemInUse); mState=(State)(mState & ~Flushed); } for(;;) { if ((mState & (state|Flushed))!=0) // bit patterns allowed { PRINTCB(("Detected state %X, wanted %X, returning", mState, state)); return; } PRINTCB(("Waiting for %X; headroom=%u tailroom=%u this=%p",state,mHeadRoom,mTailRoom,this)); // To prevent a race condition here in case the state // gets changed just after the GetState call, the next // semaphore call has a timeout. acquire_sem_etc(mSemStateChange,1,B_RELATIVE_TIMEOUT,1000000); } } // Fill buffer with data. void Fill(const BYTE **extbuf, size_t *extsize) { PRINTCB(("start: head %d tail %d headroom %d tailroom %d extsize %d buffer %p this %p", mHead, mTail, mHeadRoom, mTailRoom, *extsize, mBuffer, this)); // Make a local copy of the queue. // This is ok because there is only one filler thread and // one drainer thread. The drainer is not going to make the // free area for the filler any smaller and the filler is not // going to overwrite the drainer's data if we do this. // This way we can keep the semaphore busy as short as possible. PINDEX lTail; PINDEX lTailRoom; PINDEX lHead; // read only { Guard _(mSemInUse); // guard data integrity lTail=mTail; lTailRoom=mTailRoom; lHead=mHead; } bool needhousekeeping=false; PINDEX totaldone=0; while (*extsize!=0 && lTailRoom!=0 && totaldonemHead) { mHeadRoom=mTail-mHead; } else { mHeadRoom=mSize-mHead; } // Check if we need to change the state UpdateState(); PRINTCB((" end: head %d tail %d headroom %d tailroom %d extsize %d", mHead, mTail, mHeadRoom, mTailRoom, *extsize)); } } // Empty data out of buffer void Drain(BYTE **extbuf, size_t *extsize) { PTRACE(7, "Drain: head " << mHead << " tail " << mTail << " headroom " << mHeadRoom << " tailroom " << mTailRoom << " extsize " << *extsize << " buffer " << mBuffer << " this " << this); // Make a local copy of the queue. // This is ok because there is only one filler thread and // one drainer thread. The drainer is not going to make the // free area for the filler any smaller and the filler is not // going to overwrite the drainer's data if we do this. // This way we can keep the semaphore busy as short as possible. PINDEX lHead; PINDEX lHeadRoom; PINDEX lTail; // read only { Guard _(mSemInUse); // guard data integrity lHead=mHead; lHeadRoom=mHeadRoom; lTail=mTail; } bool needhousekeeping=false; PINDEX totaldone=0; while (*extsize!=0 && lHeadRoom!=0 && totaldonemTail) { mTailRoom=mHead-mTail; } else { mTailRoom=GetSize()-mTail; } // Check if we need to change the state UpdateState(); PRINTCB((" end: head %d tail %d headroom %d tailroom %d extsize %d", mHead, mTail, mHeadRoom, mTailRoom, *extsize)); } } }; //////////////////////////////////////////////////////////////////////////////// class ResamplingBuffer : public CircularBuffer { protected: Resampler *mResampler; protected: virtual size_t Write( BYTE *dest, // destination const BYTE **extbuf, // source, to be updated size_t size, // space in destination size_t *extsize) // data in source, to be updated { size_t todo=*extsize/mResampler->InFrameSize(); size_t done=mResampler->InFrames( (const short **)extbuf, (short **)&dest, &todo, size/mResampler->OutFrameSize()); done*=mResampler->OutFrameSize(); *extsize=todo*mResampler->InFrameSize(); return done; } public: void SetResampler(Resampler *resampler) { Guard _(mSemInUse); // guard data integrity mResampler=resampler; } ResamplingBuffer( Resampler *resampler, PINDEX size, PINDEX fillthreshold=0, PINDEX drainthreshold=0) : CircularBuffer(size, fillthreshold, drainthreshold), mResampler(NULL) { SetResampler(resampler); } ResamplingBuffer( Resampler *resampler, CircularBuffer *other) : CircularBuffer(other->mSize, other->mFillThreshold, other->mDrainThreshold), mResampler(NULL) { SetResampler(resampler); } }; //////////////////////////////////////////////////////////////////////////////// static void PlayBuffer(void *cookie, void *buffer, size_t size, const media_raw_audio_format &format) { // This function is called by the BSoundPlayer object whenever it needs some more // data to play. DETECTVARS(buffer, size/2) ((CircularBuffer *)cookie)->Drain((BYTE **)&buffer, &size); DETECTSOUND(); } static void RecordBuffer(void *cookie, const void *buffer, size_t size, const media_header &header) { // This function is called by the BMediaRecorder object whenever it has a buffer // with recorded data ready. DETECTVARS(buffer, size/2) DETECTSOUND(); ((CircularBuffer *)cookie)->Fill((const BYTE **)&buffer, &size); } //////////////////////////////////////////////////////////////////////////////// // PSoundChannelBeOS // This defines the number of times we would like to be called per second // to play/record data #define PLAYRECFREQ 20 // Macro to let the default buffer size correspond neatly with the // setting we put into the format. #define DEFAULT_BUFSIZE(channels, rate, bits) 480 //((channels*rate*(bits/8))/PLAYRECFREQ) PSoundChannelBeOS::PSoundChannelBeOS() : mRecorder(NULL), mPlayer(NULL), mBuffer(NULL), mNumBuffers(1), mResampler(NULL) { PRINT(("default constructor")); InternalSetBuffers(DEFAULT_BUFSIZE(1, 8000, 16),DEFAULT_BUFSIZE(1, 8000, 16)/2); SetFormat(1, 8000, 16); // Nothing else to do here. Notice that the channel is not open for // playing/recording yet. } PSoundChannelBeOS::PSoundChannelBeOS(const PString & dev, Directions dir, unsigned numChannels, unsigned sampleRate, unsigned bitsPerSample) : mRecorder(NULL), mPlayer(NULL), mBuffer(NULL), mNumBuffers(1), mResampler(NULL) { PRINT(("constructor %s %u %u %u", dir==Player ? "Player" : "Recorder", numChannels, sampleRate, bitsPerSample)); InternalSetBuffers(DEFAULT_BUFSIZE(numChannels, sampleRate, bitsPerSample), DEFAULT_BUFSIZE(numChannels, sampleRate, bitsPerSample)/2); Open(dev, dir, numChannels, sampleRate, bitsPerSample); // ignore result; user will need to find out whether this succeeds using IsOpen } PSoundChannelBeOS::~PSoundChannelBeOS() { PRINT(("")); Close(); // destroys player and recorder InternalSetBuffers(0,0); // destroys buffer } static const PStringArray GetRecorderDevicesList(BMediaRecorder *Recorder) { // Array to hold the list. PStringArray devlist; BMediaRecorder* bRecorder = NULL; if(Recorder != NULL) bRecorder = Recorder; #ifdef MEDIA_KIT_UPDATE BMediaRecorder localRecorder("GetRecorderDevicesList"); bool result=true; status_t status; { if(bRecorder == NULL) { bRecorder = &localRecorder; } if (bRecorder == NULL || bRecorder->InitCheck()!=B_OK) { PRINT(("Error constructing recorder to fetch device names")); result=false; } } if (result) { media_format format; format.type = B_MEDIA_RAW_AUDIO; format.u.raw_audio=media_raw_audio_format::wildcard; // The resampler can only handle 16-bit audio format.u.raw_audio.format=media_raw_audio_format::B_AUDIO_SHORT; // Let the media recorder determine which sources are available if ((status = bRecorder->FetchSources(format, false))!=B_OK) { PRINT(("Couldn't fetch BMediaRecorder sources; status=%d", status)); result=false; } } if (result) { // Fetch the names of all output devices media_format format; BString outname; for (int i=0; i< bRecorder->CountSources(); i++) { if ((status = bRecorder->GetSourceAt(i, &outname, &format))==B_OK) { PRINT(("Device found: %s", outname.String())); devlist[i] = PString(outname.String()); } else { PRINT(("error %d retrieving data for device %d", status, i)); result=false; } } } if (!result) { devlist.RemoveAll(); } return devlist; #else // Media Kit is the only device devlist[0] = "MediaKit"; return devlist; #endif } PStringArray PSoundChannelBeOS::GetDeviceNames(Directions dir) { if (dir==Recorder) { return GetRecorderDevicesList(NULL); } else { // not supported yet return PStringArray("MediaKit"); } } PString PSoundChannelBeOS::GetDefaultDevice(Directions dir) { if (dir==Recorder) { const PStringArray &devlist = GetRecorderDevicesList(NULL); if (devlist.GetSize()!=0) { return devlist[0]; } else { return PString("MediaKit"); } } else { // not supported yet return PString("MediaKit"); } } BOOL PSoundChannelBeOS::OpenPlayer(void) { // We're using cascaded "if result"s here for clarity BOOL result = TRUE; #ifdef FILEDUMP media_format format; format.type=B_MEDIA_RAW_AUDIO; memcpy(&format.u.raw_audio, &mFormat, sizeof(mFormat)); delete playwriter; playwriter=new BAudioFileWriter("play.wav", format, 441000); #endif // Must have a buffer if (!mBuffer) { result = FALSE; PRINT(("Trying to open as player without setting buffers first")); } if (result) { // Create the player //was: mPlayer=new BSoundPlayer(&mFormat, NULL, PlayBuffer, NULL, mBuffer); mPlayer = new BSoundPlayer( &mFormat, NULL, PlayBuffer, NULL, mBuffer); if ((mPlayer == NULL) || (mPlayer->InitCheck() != B_OK)) { result = FALSE; PRINT(("Couldn't construct player")); } } if (result) { // Start the player if (mPlayer->Start() != B_OK) { result = FALSE; PRINT(("Couldn't start the player")); } } if (result) { // Enable the fetching of data by PlayBuffer mPlayer->SetHasData(true); } PRINT(("Returning %s", result?"success":"failure")); return result; } BOOL PSoundChannelBeOS::OpenRecorder(const PString &dev) { // We're using cascaded "if result"s here for clarity BOOL result=TRUE; { if (!mBuffer) { result=FALSE; PRINT(("Trying to open as recorder without setting buffers first")); } } if (result) { // Create the recorder mRecorder=new BMediaRecorder("PWLIB PSoundChannel recorder"); if ((mRecorder==NULL) || (mRecorder->InitCheck()!=B_OK)) { result=FALSE; PRINT(("Couldn't construct recorder")); } } #ifdef MEDIA_KIT_UPDATE int32 sourceindex; if (result) { // Find the specified device in the list of input devices PINDEX x=GetRecorderDevicesList(mRecorder).GetStringsIndex(dev); if (x==P_MAX_INDEX) { result=FALSE; PRINT(("Couldn't find device %s in the list",(const char *)dev)); } else { sourceindex=(int32)x; } } #ifdef _DEBUG if (result) { // Get information for the device BString outname; media_format xformat; status_t err; if ((err=mRecorder->GetSourceAt(sourceindex, &outname, &xformat))==B_OK) { PRINT(("%s", outname.String())); PRINT((" type %d", (int)xformat.type)); PRINT((" AudioFormat 0x%X", (int)xformat.AudioFormat())); PRINT((" u.raw_audio:")); PRINT((" frame_rate: %f", xformat.u.raw_audio.frame_rate)); PRINT((" channel_count: %d", xformat.u.raw_audio.channel_count)); PRINT((" byte_order: %d", xformat.u.raw_audio.byte_order)); PRINT((" buffer_size: %d", xformat.u.raw_audio.buffer_size)); } else { result=FALSE; PRINT(("couldn't get details for source %d: err=0x%X",sourceindex,err)); } } #endif if (result) { // Try to connect to the source if (mRecorder->ConnectSourceAt(sourceindex)!=B_OK) { result=FALSE; PRINT(("Couldn't connect BMediaRecorder to source")); } } #else if (result) { // Connect the recorder to the default input device media_format format; format.type=B_MEDIA_RAW_AUDIO; format.u.raw_audio=media_raw_audio_format::wildcard; // The resampler can only handle 16-bit audio format.u.raw_audio.format=media_raw_audio_format::B_AUDIO_SHORT; if (mRecorder->Connect(format,0)!=B_OK) { result=FALSE; PRINT(("couldn't connect the recorder to the default source")); } } #endif if (result) { // Create resampler media_format format=mRecorder->Format(); delete mResampler; mResampler=new Resampler( format.u.raw_audio.frame_rate, mFormat.frame_rate, format.u.raw_audio.channel_count, mFormat.channel_count, 0, 2); #ifdef FILEDUMP { media_format format; format.type=B_MEDIA_RAW_AUDIO; memcpy(&format.u.raw_audio, &mFormat, sizeof(mFormat)); delete recwriter; recwriter=new BAudioFileWriter("record.wav", format); } #endif // If the current buffer is not a resamplin buffer, re-create it ResamplingBuffer *buf=dynamic_cast(mBuffer); if (buf==NULL) { PRINT(("re-creating buffer")); CircularBuffer *old=mBuffer; mBuffer=new ResamplingBuffer(mResampler, old); delete old; } else { buf->SetResampler(mResampler); } } if (result) { // Set the hook function to our data processing function PRINT(("Setting buffer hook, cookie=%p",mBuffer)); if (mRecorder->SetBufferHook(RecordBuffer, mBuffer)!=B_OK) { result=FALSE; PRINT(("Couldn't set buffer hook on BMediaRecorder")); } } // If something went wrong, delete the recorder. if (!result) { if (mRecorder) { delete mRecorder; mRecorder=NULL; } } return result; } BOOL PSoundChannelBeOS::Open(const PString & dev, Directions dir, unsigned numChannels, unsigned sampleRate, unsigned bitsPerSample) { // We're using cascaded "if result"s here for clarity BOOL result = TRUE; PRINT(("%s %u %u %u", dir==Player?"Player":"Recorder", numChannels, sampleRate, bitsPerSample)); // Close the channel first, just in case Close(); // Initialize the format struct, necessary to create player or recorder if (!SetFormat(numChannels, sampleRate, bitsPerSample)) { result = FALSE; PRINT(("Couldn't set format")); } if (result) { switch (dir) { case Player: PRINT(("... trying to open player")); result=OpenPlayer(); break; case Recorder: PRINT(("...trying to open recorder")); result=OpenRecorder(dev); break; default: PRINT(("Unknown direction parameter")); result=FALSE; } } if (!result) { // If anything went wrong, clean up PRINT(("... can't open, cleaning up")); Close(); } ::snooze(1*1000*1000); PRINT(("Returning %s", result?"success":"failure")); return result; } BOOL PSoundChannelBeOS::Abort() { return FALSE; } BOOL PSoundChannelBeOS::SetFormat(unsigned numChannels, unsigned sampleRate, unsigned bitsPerSample) { PRINT(("%u %u %u", numChannels, sampleRate, bitsPerSample)); // NOTE: all constructors should call this to initialize // the local members // Do NOT call the function with any parameter set to 0! // The function only fails if the channel is open. // This is because the player or recorder needs to be re-created when the // format changes. if (IsOpen()) { PRINT(("Not allowed to set format on open channel")); return FALSE; } // Initialize the format struct // The numbers of bits that we support here are 8, 16 or 32 bits (signed), // results for other sizes are not defined. mFormat = media_raw_audio_format::wildcard; mFormat.frame_rate=(float)sampleRate; mFormat.channel_count=numChannels; mFormat.format=(bitsPerSample / 8) & 0xF; mFormat.byte_order=B_HOST_IS_BENDIAN ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; mFormat.buffer_size=DEFAULT_BUFSIZE(numChannels, sampleRate, bitsPerSample); return TRUE; } unsigned PSoundChannelBeOS::GetChannels() const { return mFormat.channel_count; } unsigned PSoundChannelBeOS::GetSampleRate() const { return (unsigned)mFormat.frame_rate; } unsigned PSoundChannelBeOS::GetSampleSize() const { return (mFormat.format & 0xF)*8; // return number of BITS } BOOL PSoundChannelBeOS::Read(void *buf, PINDEX len) { PINDEX bufSize = len; // Can only read from a recorder if (mRecorder!=NULL) { // A Read starts the recording, if it's not running already if (!mRecorder->IsRunning()) { mRecorder->Start(); } // Wait until there's a buffer recorded mBuffer->WaitForState(CircularBuffer::NotEmpty); #ifdef FILEDUMP void *dumpbuf=buf; size_t dumpsize=len; #endif lastReadCount = 0; while(lastReadCount < bufSize) { len = bufSize - lastReadCount; if(len <= 0) break; // Get data from the buffer mBuffer->Drain((BYTE**)&buf, (size_t*) &len); lastReadCount += len; } #ifdef FILEDUMP if (recwriter) { recwriter->writewavfile(dumpbuf, dumpsize); } #endif return TRUE; } return FALSE; } BOOL PSoundChannelBeOS::Write(const void *buf, PINDEX len) { // can only write to a player if (mPlayer!=NULL) { // Wait until there is space mBuffer->WaitForState(CircularBuffer::EmptyEnough); // This function needs to update the last write count // Store len before it gets modified lastWriteCount=len; #ifdef FILEDUMP if (playwriter) { playwriter->writewavfile(buf,len); } #endif // Store data into the buffer mBuffer->Fill((const BYTE **)&buf, (size_t*) &len); // Update last write count lastWriteCount-=len; return TRUE; } return FALSE; } BOOL PSoundChannelBeOS::Close() { PRINT(("")); // Flush the buffer first Abort(); // Stop the player if ((mPlayer!=NULL) && (mPlayer->InitCheck()==B_OK)) { mPlayer->Stop(); #ifdef FILEDUMP delete playwriter; playwriter=NULL; #endif } if(mPlayer) { // Destroy the player delete mPlayer; mPlayer=NULL; // make sure that another Close won't crash the system } // Stop the recorder if ((mRecorder!=NULL) && (mRecorder->InitCheck()==B_OK)) { mRecorder->Stop(); // Not really necessary mRecorder->Disconnect(); #ifdef FILEDUMP delete recwriter; recwriter=NULL; #endif } if(mRecorder) { // Destroy the recorder delete mRecorder; mRecorder=NULL; // make sure that another Close won't crash the system } return TRUE; } BOOL PSoundChannelBeOS::SetBuffers(PINDEX size, PINDEX count) { return InternalSetBuffers(size*(mNumBuffers=count),size); } BOOL PSoundChannelBeOS::InternalSetBuffers(PINDEX size, PINDEX threshold) { if (mPlayer) { mPlayer->SetHasData(false); } else if (mRecorder) { mRecorder->Stop(); } // Delete the current buffer if(mBuffer != NULL) { delete mBuffer; mBuffer = NULL; } // Create the new buffer if (size != 0) { if (mRecorder) { if (!mResampler) { PTRACE(TL, "Creating default resampler"); mResampler = new Resampler(1.0,1.0,1,1,0,1); } PTRACE(TL, "Creating resampling buffer, size " << size); mBuffer = new ResamplingBuffer(mResampler, size, threshold, threshold); // In case we use resampler, size must be set to resampled buffer size } else { PTRACE(TL, "Creating playback buffer, size " << size); mBuffer = new CircularBuffer(size, threshold, threshold); } // If we have a player, set the cookie again and restart it if (mPlayer) { mPlayer->SetCookie(mBuffer); PTRACE(TL, "Tried to set player buffer cookie"); mPlayer->SetHasData(true); PTRACE(TL, "Tried to set player has data"); } // If we have a recorder, set the cookie again // Note that the recorder is not restarted, even if it was running. // It's not a good idea for the program to change the buffers during // recording anyway because it would at least lose some data. if (mRecorder) { if(B_OK != mRecorder->SetBufferHook(RecordBuffer, mBuffer)) PTRACE(TL, "Can't set recorder buffer hook"); } return TRUE; } if (IsOpen()) { PTRACE(TL, "Can't continue without buffers - closing channel"); Close(); // should give errors on subsequent read/writes } mBuffer = NULL; return FALSE; } BOOL PSoundChannelBeOS::GetBuffers(PINDEX &size, PINDEX &count) { if (mBuffer) { size=mBuffer->GetSize(); count=mNumBuffers; return TRUE; } return FALSE; } BOOL PSoundChannelBeOS::PlaySound(const PSound &sound, BOOL wait) { PRINT(("wait=%s", wait?"true":"false")); if (mPlayer==NULL) { PRINT(("Playing a sound on a closed (or recording) PSoundChannelBeOS")); return FALSE; } #ifdef FILEDUMP playwriter->writewavfile((void *)(const BYTE*)sound, sound.GetSize()); #endif // create a local buffer that references the PSound // NOTE: no conversion between the PSound's format and the // PSoundChannelBeOS's format is done. const BYTE *buf=(const BYTE *)sound; PINDEX size=sound.GetSize(); // Play the sound by doing successive Writes until the sound is done. // Note that write will return when either the buffer is full or the // given data is written. We want to return after the entire sound // has been buffered. So we repeatedly call Write until there is // no data left while (size!=0) { // Wait until there is space mBuffer->WaitForState(CircularBuffer::EmptyEnough); // Write the data mBuffer->Fill(&buf, (size_t*) &size); } // Wait until the sound is finished, if requested if (wait) { PRINT(("Waiting for sound")); mBuffer->WaitForState(CircularBuffer::Empty); } return TRUE; } BOOL PSoundChannelBeOS::PlayFile(const PFilePath &file, BOOL wait) { entry_ref ref; status_t err; // using pointers for these objects so that we don't have to // construct them here but can nevertheless use the if(ok)'s BEntry *pentry = NULL; { // Create BEntry from file name pentry = new BEntry(file, true); err = pentry->InitCheck(); } if (err==B_OK) { // Create entry_ref from BEntry err = pentry->GetRef(&ref); } if (err==B_OK) { // Play the sound. Return value is a handle or a negative value for errors // Errors in BeOS are always negative values err=play_sound(&ref, true, !wait, wait); if (err>=0) { err=B_OK; } } return (err==B_OK); } BOOL PSoundChannelBeOS::HasPlayCompleted() { if (mPlayer!=NULL) { return mBuffer->IsEmpty(); } return FALSE; } BOOL PSoundChannelBeOS::WaitForPlayCompletion() { if (mPlayer!=NULL) { mBuffer->WaitForState(CircularBuffer::Empty); } return TRUE; } BOOL PSoundChannelBeOS::RecordSound(PSound &sound) { PRINT(("")); if (mRecorder==NULL) { PRINT(("Recording a sound on a closed (or playing) PSoundChannelBeOS")); return FALSE; } // Flush the buffer first Abort(); // Start recording if (mRecorder->Start()!=B_OK) { PRINT(("BMediaRecorder::Start() returned error")); return FALSE; } // Wait until buffer is filled mBuffer->WaitForState(CircularBuffer::Full); PRINT(("Buffer full: size=%lu",mBuffer->GetSize())); // Stop the recorder if (mRecorder->Stop()!=B_OK) { PRINT(("Uh-oh, recorder is unstoppable!")); //return FALSE; } // Set the sound's format to ours sound.SetFormat(GetChannels(), GetSampleRate(), GetSampleSize()); // Resize the sound and set up local buffer references PINDEX size=mBuffer->GetSize(); BYTE *buf=sound.GetPointer(size); #ifdef FILEDUMP void *dumpbuf=buf; size_t dumpsize=size; #endif // Read the data mBuffer->Drain(&buf, (size_t*) &size); #ifdef FILEDUMP recwriter->writewavfile(dumpbuf, dumpsize); #endif PRINT(("Recording succesful")); return TRUE; } BOOL PSoundChannelBeOS::RecordFile(const PFilePath & filename) { // Not implemented for now return FALSE; } BOOL PSoundChannelBeOS::StartRecording() { if (mRecorder==NULL) { PRINT(("Recording to a closed (or playing) PSoundChannelBeOS")); return FALSE; } // Flush the buffers Abort(); // Start recording if (mRecorder->Start()!=B_OK) { PRINT(("BMediaRecorder::Start returned error")); return FALSE; } return TRUE; } BOOL PSoundChannelBeOS::IsRecordBufferFull() { if (mRecorder) { return !mBuffer->IsEmpty(); } return FALSE; } BOOL PSoundChannelBeOS::AreAllRecordBuffersFull() { if (mRecorder) { return mBuffer->IsFull(); } return FALSE; } BOOL PSoundChannelBeOS::WaitForRecordBufferFull() { if (mRecorder==NULL) { PRINT(("Waiting for record buffer on playing or closed PSoundChannelBeOS")); return FALSE; } mBuffer->WaitForState(CircularBuffer::FullEnough); return PXSetIOBlock(PXReadBlock, readTimeout); } BOOL PSoundChannelBeOS::WaitForAllRecordBuffersFull() { if (mRecorder==NULL) { PRINT(("Waiting for record buffers on playing or closed PSoundChannelBeOS")); return FALSE; } mBuffer->WaitForState(CircularBuffer::Full); return TRUE; } BOOL PSoundChannelBeOS::IsOpen() const { BOOL result=((mPlayer!=NULL) || (mRecorder!=NULL)); PRINT(("returning %s, player 0x%X recorder 0x%X", result?"true":"false", mPlayer, mRecorder)); return result; } BOOL PSoundChannelBeOS::SetVolume(unsigned newVolume) { #ifdef TODO cerr << __FILE__<< "PSoundChannelBeOS :: SetVolume called in error. Please fix" << endl; #endif return TRUE; } BOOL PSoundChannelBeOS::GetVolume(unsigned & volume) { #ifdef TODO cerr << __FILE__<< "PSoundChannelBeOS :: GetVolume called in error. Please fix" << endl; #endif return TRUE; } // End of file