From 88130e10b351cca606d652174e1e99bab509bc37 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Sun, 10 Feb 2008 21:44:31 +0000 Subject: [PATCH] buffered audio output for files, e.g. mp3 playback git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@713 8eb893ce-cfd4-0310-b710-fb5ebe64c474 --- apps/mp3/mp3.c | 53 ++++++++++----- core/AmAudio.h | 4 +- core/AmAudioFile.cpp | 13 ++-- core/AmAudioFile.h | 3 +- core/AmBufferedAudio.cpp | 143 +++++++++++++++++++++++++++++++++++++++ core/AmBufferedAudio.h | 63 +++++++++++++++++ core/amci/amci.h | 9 +++ 7 files changed, 266 insertions(+), 22 deletions(-) create mode 100644 core/AmBufferedAudio.cpp create mode 100644 core/AmBufferedAudio.h diff --git a/apps/mp3/mp3.c b/apps/mp3/mp3.c index 0e7af457..e2c479ac 100644 --- a/apps/mp3/mp3.c +++ b/apps/mp3/mp3.c @@ -182,8 +182,12 @@ long MP3_create(const char* format_parameters, amci_codec_fmt_info_t* format_des ERROR("initializing mpg123 decoder instance\n"); return -1; } -/* decoded_pos = 0; */ -/* coded_pos = 0; */ + + /* suppress output */ + mpg123_param(coder_state->mpg123_h, MPG123_FLAGS, MPG123_QUIET /* | MPG123_FORCE_MONO */,0); + +/* mpg123_param(coder_state->mpg123_h, MPG123_VERBOSE, 0, 0); */ + #endif return (long)coder_state; @@ -196,6 +200,8 @@ void MP3_destroy(long h_inst) { #ifdef WITH_MPG123DECODER mpg123_delete(((mp3_coder_state*)h_inst)->mpg123_h); #endif + + free((mp3_coder_state*)h_inst); } } @@ -242,15 +248,22 @@ static unsigned int mp3_samples2bytes(long h_codec, unsigned int num_samples) #ifndef WITH_MPG123DECODER return num_samples; #else - // requets a full frame - // we don't know bitrate - so use 128000 as max bitrate - // 144 * BitRate / (SampleRate + Padding) - - unsigned int res = 144 * 128000 / (((mp3_coder_state*)h_codec)->rate + 1); - if (res > AUDIO_BUFFER_SIZE) - res = AUDIO_BUFFER_SIZE; - return res; + /* + * 150 bytes is about one frame as produced by the mp3 writer. + * for higher bitrate files this will only introduce the small performance + * penalty of multiple read() calls per frame-decode. + */ + return 150; + +/* or a full frame? */ +/* we don't know bitrate - so use 128000 as max bitrate */ +/* 144 * BitRate / (SampleRate + Padding) */ +/* unsigned int res = 144 * 128000 / (((mp3_coder_state*)h_codec)->rate + 1); */ +/* if (res > AUDIO_BUFFER_SIZE) */ +/* res = AUDIO_BUFFER_SIZE; */ +/* return res; */ + #endif } @@ -277,13 +290,14 @@ static int MP3_2_Pcm16( unsigned char* out_buf, unsigned char* in_buf, unsigned out_buf, AUDIO_BUFFER_SIZE, &decoded_size); if (res == MPG123_NEW_FORMAT) { - WARN("mp3 file format change!\n"); + WARN("intermediate mp3 file format change!\n"); } if (res == MPG123_ERR) { - ERROR("decoding mp3!\n"); + ERROR("decoding mp3: '%s'\n", + mpg123_strerror(coder_state->mpg123_h)); return -1; } - +/* DBG("mp3: decoded %d\n", decoded_size); */ return decoded_size; #endif } @@ -321,18 +335,20 @@ static int MP3_open(FILE* fp, struct amci_file_desc_t* fmt_desc, int options, lo DBG("Initializing mpg123 codec state.\n"); res = mpg123_open_feed(coder_state->mpg123_h); if (MPG123_ERR == res) { - ERROR("mpg123_open_feed returned mpg123 error.\n"); + ERROR("mpg123_open_feed returned mpg123 error '%s'\n", + mpg123_strerror(coder_state->mpg123_h)); return -1; } + /* read until we know the format */ while (res!= MPG123_NEW_FORMAT) { - DBG("safe_read 20 of fp %ld\n", (long)fp); SAFE_READ(mp_rd_buf, 20, fp, sr); res = mpg123_decode(coder_state->mpg123_h, mp_rd_buf, 20, mp_rd_buf, 0, &decoded_size); if (res == MPG123_ERR) { - ERROR("trying to determine MP3 file format.\n"); + ERROR("trying to determine MP3 file format: '%s'\n", + mpg123_strerror(coder_state->mpg123_h)); return -1; } } @@ -348,6 +364,11 @@ static int MP3_open(FILE* fp, struct amci_file_desc_t* fmt_desc, int options, lo fmt_desc->channels = coder_state->channels; fmt_desc->data_size = -1; + /* set buffering parameters */ + fmt_desc->buffer_size = MP3_FRAMESAMPLES * 8; + fmt_desc->buffer_thresh = MP3_FRAMESAMPLES; + fmt_desc->buffer_full_thresh = MP3_FRAMESAMPLES * 3; + return 0; #endif } diff --git a/core/AmAudio.h b/core/AmAudio.h index e2c7af2e..f8e79249 100644 --- a/core/AmAudio.h +++ b/core/AmAudio.h @@ -206,7 +206,7 @@ private: protected: /** Sample buffer. */ DblBuffer samples; - + /** Audio format. @see AmAudioFormat */ auto_ptr fmt; @@ -287,6 +287,8 @@ public: void setRecordTime(unsigned int ms); int incRecordTime(unsigned int samples); + + void setBufferedOutput(unsigned int buffer_size); }; diff --git a/core/AmAudioFile.cpp b/core/AmAudioFile.cpp index e68b4104..e14b1eec 100644 --- a/core/AmAudioFile.cpp +++ b/core/AmAudioFile.cpp @@ -132,8 +132,7 @@ int AmAudioFile::open(const string& filename, OpenMode mode, bool is_tmp) ERROR("could not create/overwrite file: %s\n",filename.c_str()); return -1; } - } else { - + } else { fp = tmpfile(); if(!fp){ ERROR("could not create temporary file: %s\n",strerror(errno)); @@ -141,6 +140,8 @@ int AmAudioFile::open(const string& filename, OpenMode mode, bool is_tmp) } amci_file_desc_t fd; + memset(&fd, 0, sizeof(amci_file_desc_t)); + int ret = -1; if(open_mode == AmAudioFile::Write){ @@ -159,13 +160,16 @@ int AmAudioFile::open(const string& filename, OpenMode mode, bool is_tmp) fd.channels = f_fmt->channels; fd.rate = f_fmt->rate; - if( iofmt->open && !(ret = (*iofmt->open)(fp, &fd, mode, f_fmt->getHCodecNoInit())) ) { + if( iofmt->open && + !(ret = (*iofmt->open)(fp, &fd, mode, f_fmt->getHCodecNoInit())) ) { if (mode == AmAudioFile::Read) { f_fmt->setSubtypeId(fd.subtype); f_fmt->channels = fd.channels; f_fmt->rate = fd.rate; data_size = fd.data_size; + + setBufferSize(fd.buffer_size, fd.buffer_thresh, fd.buffer_full_thresh); } begin = ftell(fp); } @@ -255,7 +259,7 @@ int AmAudioFile::fpopen(const string& filename, OpenMode mode, FILE* n_fp) AmAudioFile::AmAudioFile() - : AmAudio(), data_size(0), + : AmBufferedAudio(0, 0, 0), data_size(0), fp(0), begin(0), loop(false), on_close_done(false), close_on_exit(true) @@ -270,6 +274,7 @@ AmAudioFile::~AmAudioFile() void AmAudioFile::rewind() { fseek(fp,begin,SEEK_SET); + clearBufferEOF(); } void AmAudioFile::on_close() diff --git a/core/AmAudioFile.h b/core/AmAudioFile.h index 8246a7c3..e88f09cf 100644 --- a/core/AmAudioFile.h +++ b/core/AmAudioFile.h @@ -29,6 +29,7 @@ #define _AmAudioFile_h_ #include "AmAudio.h" +#include "AmBufferedAudio.h" /** \brief \ref AmAudioFormat for file */ class AmAudioFileFormat: public AmAudioFormat @@ -74,7 +75,7 @@ public: /** * \brief AmAudio implementation for file access */ -class AmAudioFile: public AmAudio +class AmAudioFile: public AmBufferedAudio { public: /** Open mode. */ diff --git a/core/AmBufferedAudio.cpp b/core/AmBufferedAudio.cpp new file mode 100644 index 00000000..5a39ed23 --- /dev/null +++ b/core/AmBufferedAudio.cpp @@ -0,0 +1,143 @@ +/* + * $Id: AmAudio.h 711 2008-02-10 18:52:44Z sayer $ + * + * Copyright (C) 2002-2003 Fhg Fokus + * + * This file is part of sems, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the ser software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * sems is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "AmBufferedAudio.h" + +AmBufferedAudio::AmBufferedAudio(size_t output_buffer_size, + size_t low_buffer_thresh, size_t full_buffer_thresh) + : output_buffer_size(output_buffer_size), + low_buffer_thresh(low_buffer_thresh), full_buffer_thresh(full_buffer_thresh), + r(0), w(0), eof(false), err_code(0) +{ + allocateBuffer(); +} + +AmBufferedAudio::~AmBufferedAudio() { + releaseBuffer(); +} + +void AmBufferedAudio::allocateBuffer() { + if (output_buffer_size != 0) + output_buffer = new unsigned char[output_buffer_size]; + else + output_buffer = NULL; +} + +void AmBufferedAudio::releaseBuffer() { + if (output_buffer) + delete[] output_buffer; +} + +// WARNING: do not call this while device is in use! buffer is not locked! +void AmBufferedAudio::setBufferSize(size_t _output_buffer_size, + size_t _low_buffer_thresh, + size_t _full_buffer_thresh) { + + DBG("set output buffer size to %zd low thresh %zd, fill thresh %zd\n", + _output_buffer_size, _low_buffer_thresh, _full_buffer_thresh); + + bool reset_buffer = output_buffer_size != _output_buffer_size; + output_buffer_size = _output_buffer_size; + low_buffer_thresh = _low_buffer_thresh; + full_buffer_thresh = _full_buffer_thresh; + + if (reset_buffer) { + releaseBuffer(); + allocateBuffer(); + } +} + +void AmBufferedAudio::clearBufferEOF() { + eof = false; + err_code = 0; +} + +int AmBufferedAudio::get(unsigned int user_ts, unsigned char* buffer, unsigned int nb_samples) { + if (!output_buffer_size) + return AmAudio::get(user_ts, buffer, nb_samples); + + if (w-r < low_buffer_thresh && !eof) { + input_get_audio(user_ts); + } + + size_t nget = PCM16_S2B(nb_samples); + if (w-r < nget) + nget = w-r; + + if (!nget) { + // empty buffer and input error + if (eof) + return err_code; + + // empty buffer but no input error + return 0; + } + + memcpy(buffer, &output_buffer[r], nget); + + r+=nget; + return nget; +} + +void AmBufferedAudio::input_get_audio(unsigned int user_ts) { + if (r && (r != w)) { + // move contents to beginning of buffer + memmove(output_buffer, &output_buffer[r], w-r); + w -= r; + r = 0; + } + while (w < full_buffer_thresh) { + int size = calcBytesToRead(PCM16_B2S(output_buffer_size - w)); + +// DBG("calc %d bytes to read\n", size); + + // resync might be delayed until buffer empty // (but output resync never happens) + size = read(user_ts + PCM16_B2S(w-r),size); +// DBG("read returned size = %d\n",size); + if(size <= 0){ + err_code = size; + eof = true; + return; + } + + size = decode(size); + if(size < 0) { +// DBG("decode returned %i\n",size); + err_code = size; + eof = true; + return; + } + +// DBG("decode returned %i\n",size); + size = downMix(size); + + if(size>0) { + memcpy(&output_buffer[w],(unsigned char*)samples,size); + w+=size; + } + } +} diff --git a/core/AmBufferedAudio.h b/core/AmBufferedAudio.h new file mode 100644 index 00000000..10a3fc38 --- /dev/null +++ b/core/AmBufferedAudio.h @@ -0,0 +1,63 @@ +/* + * $Id: AmAudio.h 711 2008-02-10 18:52:44Z sayer $ + * + * Copyright (C) 2002-2003 Fhg Fokus + * + * This file is part of sems, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the ser software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * sems is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _AmBufferedAudio_H +#define _AmBufferedAudio_H + +#include "AmAudio.h" +/** + * AmAudio with buffered output + */ +class AmBufferedAudio : public AmAudio { + + unsigned char* output_buffer; + size_t output_buffer_size, low_buffer_thresh, full_buffer_thresh; + size_t r, w; + + bool eof; + int err_code; + + void input_get_audio(unsigned int user_ts); + inline void allocateBuffer(); + inline void releaseBuffer(); + + protected: + AmBufferedAudio(size_t output_buffer_size, size_t low_buffer_thresh, size_t full_buffer_thresh); + ~AmBufferedAudio(); + + void clearBufferEOF(); + void setBufferSize(size_t _output_buffer_size, size_t _low_buffer_thresh, size_t _full_buffer_thresh); + + public: + virtual int get(unsigned int user_ts, unsigned char* buffer, unsigned int nb_samples); + +}; +#endif + +// Local Variables: +// mode:C++ +// End: diff --git a/core/amci/amci.h b/core/amci/amci.h index 9cd2d604..e34267b0 100644 --- a/core/amci/amci.h +++ b/core/amci/amci.h @@ -92,6 +92,15 @@ struct amci_file_desc_t { /** size of the data. -1 for unknown/unlimited */ int data_size; + + /** output buffer size. 0 for no buffering */ + int buffer_size; + + /** output buffer refill threshold */ + int buffer_thresh; + + /** output buffer refill threshold */ + int buffer_full_thresh; }; /**