From a8d9b65484437c311f3464e6401da3ae8332c9b3 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Fri, 24 Mar 2006 20:41:20 +0000 Subject: Added WAVE writer and resample port, and also found out why audio quality is poor with DirectSound git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@358 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/build/Makefile | 6 +- pjmedia/build/pjmedia.dsp | 16 +- pjmedia/include/pjmedia.h | 1 + pjmedia/include/pjmedia/config.h | 8 + pjmedia/include/pjmedia/errno.h | 5 + pjmedia/include/pjmedia/file_port.h | 44 +++- pjmedia/include/pjmedia/resample.h | 70 +++++- pjmedia/include/pjmedia/wave.h | 27 +++ pjmedia/src/pjmedia/conference.c | 80 ++++++- pjmedia/src/pjmedia/errno.c | 1 + pjmedia/src/pjmedia/file_player.c | 396 +++++++++++++++++++++++++++++++ pjmedia/src/pjmedia/file_port.c | 448 ------------------------------------ pjmedia/src/pjmedia/file_reader.c | 448 ------------------------------------ pjmedia/src/pjmedia/file_writer.c | 306 ++++++++++++++++++++++++ pjmedia/src/pjmedia/pasound.c | 45 ++-- pjmedia/src/pjmedia/port.c | 24 +- pjmedia/src/pjmedia/resample.c | 12 +- pjmedia/src/pjmedia/resample_port.c | 185 +++++++++++++++ pjmedia/src/pjmedia/sound_port.c | 3 + pjmedia/src/pjmedia/wave.c | 57 +++++ 20 files changed, 1242 insertions(+), 940 deletions(-) create mode 100644 pjmedia/src/pjmedia/file_player.c delete mode 100644 pjmedia/src/pjmedia/file_port.c delete mode 100644 pjmedia/src/pjmedia/file_reader.c create mode 100644 pjmedia/src/pjmedia/file_writer.c create mode 100644 pjmedia/src/pjmedia/resample_port.c create mode 100644 pjmedia/src/pjmedia/wave.c (limited to 'pjmedia') diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile index 7cf6012f..54a4f59e 100644 --- a/pjmedia/build/Makefile +++ b/pjmedia/build/Makefile @@ -64,9 +64,9 @@ export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJMEDIA_LIB)) \ # export PJMEDIA_SRCDIR = ../src/pjmedia export PJMEDIA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ - codec.o conference.o endpoint.o errno.o file_port.o \ - g711.o jbuf.o null_port.o port.o resample.o rtcp.o \ - rtp.o sdp.o sdp_cmp.o sdp_neg.o session.o silencedet.o \ + codec.o conference.o endpoint.o errno.o file_player.o \ + g711.o jbuf.o null_port.o port.o resample.o resample_port.o \ + rtcp.o rtp.o sdp.o sdp_cmp.o sdp_neg.o session.o silencedet.o \ sound_port.o stream.o $(SOUND_OBJS) $(NULLSOUND_OBJS) export PJMEDIA_CFLAGS += $(_CFLAGS) diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp index a84395a0..43d09f7c 100644 --- a/pjmedia/build/pjmedia.dsp +++ b/pjmedia/build/pjmedia.dsp @@ -65,7 +65,7 @@ LIB32=link.exe -lib # PROP Intermediate_Dir ".\output\pjmedia-i386-win32-vc6-debug" # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../src/pjmedia/portaudio" /D "_DEBUG" /D "PA_NO_ASIO" /D "PA_NO_WMME" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /GZ /c +# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../src/pjmedia/portaudio" /D "_DEBUG" /D "PA_NO_ASIO" /D "PA_NO_DS" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /GZ /c # SUBTRACT CPP /YX # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" @@ -108,7 +108,11 @@ SOURCE=..\src\pjmedia\errno.c # End Source File # Begin Source File -SOURCE=..\src\pjmedia\file_port.c +SOURCE=..\src\pjmedia\file_player.c +# End Source File +# Begin Source File + +SOURCE=..\src\pjmedia\file_writer.c # End Source File # Begin Source File @@ -140,6 +144,10 @@ SOURCE=..\src\pjmedia\resample.c # End Source File # Begin Source File +SOURCE=..\src\pjmedia\resample_port.c +# End Source File +# Begin Source File + SOURCE=..\src\pjmedia\rtcp.c # End Source File # Begin Source File @@ -174,6 +182,10 @@ SOURCE=..\src\pjmedia\sound_port.c SOURCE=..\src\pjmedia\stream.c # End Source File +# Begin Source File + +SOURCE=..\src\pjmedia\wave.c +# End Source File # End Group # Begin Group "Header Files" diff --git a/pjmedia/include/pjmedia.h b/pjmedia/include/pjmedia.h index 8cdc0b72..34264b61 100644 --- a/pjmedia/include/pjmedia.h +++ b/pjmedia/include/pjmedia.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include diff --git a/pjmedia/include/pjmedia/config.h b/pjmedia/include/pjmedia/config.h index c81693a5..8e111045 100644 --- a/pjmedia/include/pjmedia/config.h +++ b/pjmedia/include/pjmedia/config.h @@ -68,5 +68,13 @@ #endif +/** + * Default file player/writer buffer size. + */ +#ifndef PJMEDIA_FILE_PORT_BUFSIZE +# define PJMEDIA_FILE_PORT_BUFSIZE 4000 +#endif + + #endif /* __PJMEDIA_CONFIG_H__ */ diff --git a/pjmedia/include/pjmedia/errno.h b/pjmedia/include/pjmedia/errno.h index 67c4d011..59e665d8 100644 --- a/pjmedia/include/pjmedia/errno.h +++ b/pjmedia/include/pjmedia/errno.h @@ -440,6 +440,11 @@ PJ_BEGIN_DECL * Wave file too short. */ #define PJMEDIA_EWAVETOOSHORT (PJMEDIA_ERRNO_START+182) /* 220182 */ +/** + * @hideinitializer + * Sound frame is too large for file buffer. + */ +#define PJMEDIA_EFRMFILETOOBIG (PJMEDIA_ERRNO_START+183) /* 220183 */ /************************************************************ diff --git a/pjmedia/include/pjmedia/file_port.h b/pjmedia/include/pjmedia/file_port.h index 75482c70..363f94e0 100644 --- a/pjmedia/include/pjmedia/file_port.h +++ b/pjmedia/include/pjmedia/file_port.h @@ -26,11 +26,23 @@ #include + PJ_BEGIN_DECL /** - * Create file player port. + * Create a media port to play streams from a WAV file. + * + * @param pool Pool to create memory buffers for this port. + * @param filename File name to open. + * @param flags Port creation flags. + * @param buf_size Buffer size to be allocated. If the value is zero or + * negative, the port will use default buffer size (which + * is about 4KB). + * @param user_data User data to be associated with the file player port. + * @param p_port Pointer to receive the file port instance. + * + * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool, const char *filename, @@ -41,6 +53,36 @@ PJ_DECL(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool, +/** + * Create a media port to record streams to a WAV file. Note that the port + * must be closed properly (with #pjmedia_port_destroy()) so that the WAV + * header can be filled with correct values (such as the file length). + * + * @param pool Pool to create memory buffers for this port. + * @param filename File name. + * @param flags Port creation flags. + * @param buf_size Buffer size to be allocated. If the value is zero or + * negative, the port will use default buffer size (which + * is about 4KB). + * @param user_data User data to be associated with the file writer port. + * @param p_port Pointer to receive the file port instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_file_writer_port_create( pj_pool_t *pool, + const char *filename, + unsigned sampling_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + unsigned flags, + pj_ssize_t buff_size, + void *user_data, + pjmedia_port **p_port ); + + + + PJ_END_DECL diff --git a/pjmedia/include/pjmedia/resample.h b/pjmedia/include/pjmedia/resample.h index 63b3351d..1def54ce 100644 --- a/pjmedia/include/pjmedia/resample.h +++ b/pjmedia/include/pjmedia/resample.h @@ -25,10 +25,25 @@ * @file reample.h * @brief Sample rate converter. */ -#include "pjmedia/types.h" +#include +#include + PJ_BEGIN_DECL +/* + * This file declares two types of API: + * + * Application can use #pjmedia_resample_create() and #pjmedia_resample_run() + * to convert a frame from source rate to destination rate. The inpuit frame + * must have a constant length. + * + * Alternatively, application can create a resampling port with + * #pjmedia_resample_port_create() and connect the port to other ports to + * change the sampling rate of the samples. + */ + + /** * Opaque resample session. */ @@ -59,7 +74,9 @@ PJ_DECL(pj_status_t) pjmedia_resample_create(pj_pool_t *pool, /** - * Resample a frame. + * Use the resample session to resample a frame. The frame must have the + * same size and settings as the resample session, or otherwise the + * behavior is undefined. * * @param resample The resample session. * @param input Buffer containing the input samples. @@ -70,6 +87,55 @@ PJ_DECL(void) pjmedia_resample_run( pjmedia_resample *resample, pj_int16_t *output ); +/** + * Get the input frame size of a resample session. + * + * @param resample The resample session. + * + * @return The frame size, in number of samples. + */ +PJ_DECL(unsigned) pjmedia_resample_get_input_size(pjmedia_resample *resample); + + +/** + * Create a resample port. This creates a bidirectional resample session, + * which will resample frames when the port's get_frame() and put_frame() + * is called. + * + * When the resample port's get_frame() is called, this port will get + * a frame from the downstream port and resample the frame to the upstream + * port's clock rate before returning it to the caller. + * + * When the resample port's put_frame() is called, this port will resample + * the frame to the downstream's port clock rate before giving the frame + * to the downstream port. + * + * @param pool Pool to allocate the structure and buffers. + * @param high_quality If true, then high quality conversion will be + * used, at the expense of more CPU and memory, + * because temporary buffer needs to be created. + * @param large_filter If true, large filter size will be used. + * @param downstream_rate The sampling rate of the downstream port. + * @param upstream_rate The sampling rate of the upstream port. + * @param channel_count The number of channels. This argument is only + * used for the port information. It does not + * change the behavior of the resample port. + * @param samples_per_frame Number of samples per frame from the downstream + * port. + * @param p_port Pointer to receive the resample port instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_resample_port_create( pj_pool_t *pool, + pj_bool_t high_quality, + pj_bool_t large_filter, + unsigned downstream_rate, + unsigned upstream_rate, + unsigned channel_count, + unsigned samples_per_frame, + pjmedia_port **p_port ); + + PJ_END_DECL #endif /* __PJMEDIA_RESAMPLE_H__ */ diff --git a/pjmedia/include/pjmedia/wave.h b/pjmedia/include/pjmedia/wave.h index cd4c3e61..201f9760 100644 --- a/pjmedia/include/pjmedia/wave.h +++ b/pjmedia/include/pjmedia/wave.h @@ -32,6 +32,7 @@ PJ_BEGIN_DECL #define PJMEDIA_RIFF_TAG ('F'<<24|'F'<<16|'I'<<8|'R') #define PJMEDIA_WAVE_TAG ('E'<<24|'V'<<16|'A'<<8|'W') #define PJMEDIA_FMT_TAG (' '<<24|'t'<<16|'m'<<8|'f') +#define PJMEDIA_DATA_TAG ('a'<<24|'t'<<16|'a'<<8|'d') /** @@ -69,6 +70,32 @@ struct pjmedia_wave_hdr typedef struct pjmedia_wave_hdr pjmedia_wave_hdr; +/** + * On big-endian hosts, this function swaps the byte order of the values + * in the WAVE header fields. On little-endian hosts, this function does + * nothing. + * + * Application SHOULD call this function after reading the WAVE header + * chunks from a file. + * + * @param hdr The WAVE header. + */ +PJ_DECL(void) pjmedia_wave_hdr_file_to_host( pjmedia_wave_hdr *hdr ); + + +/** + * On big-endian hosts, this function swaps the byte order of the values + * in the WAVE header fields. On little-endian hosts, this function does + * nothing. + * + * Application SHOULD call this function before writing the WAVE header + * to a file. + * + * @param hdr The WAVE header. + */ +PJ_DECL(void) pjmedia_wave_hdr_host_to_file( pjmedia_wave_hdr *hdr ); + + PJ_END_DECL #endif /* __PJMEDIA_WAVE_H__ */ diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index fad562c7..dfbe567c 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -28,15 +28,29 @@ #include #include +/* CONF_DEBUG enables detailed operation of the conference bridge. + * Beware that it prints large amounts of logs (several lines per frame). + */ //#define CONF_DEBUG #ifdef CONF_DEBUG # include -# define TRACE_(x) {printf x; fflush(stdout); } +# define TRACE_(x) PJ_LOG(5,x) #else # define TRACE_(x) #endif +/* REC_FILE macro enables recording of the samples written to the sound + * device. The file contains RAW PCM data with no header, and has the + * same settings (clock rate etc) as the conference bridge. + * This should only be enabled when debugging audio quality *only*. + */ +//#define REC_FILE "confrec.pcm" +#ifdef REC_FILE +static FILE *fhnd_rec; +#endif + + #define THIS_FILE "conference.c" #define RX_BUF_COUNT 8 @@ -966,6 +980,10 @@ static pj_status_t read_port( pjmedia_conf *conf, pj_assert(count == conf->samples_per_frame); + TRACE_((THIS_FILE, "read_port %.*s: count=%d", + (int)cport->name.slen, cport->name.ptr, + count)); + /* If port's samples per frame and sampling rate matches conference * bridge's settings, get the frame directly from the port. */ @@ -976,6 +994,10 @@ static pj_status_t read_port( pjmedia_conf *conf, f.buf = frame; f.size = count * BYTES_PER_SAMPLE; + TRACE_((THIS_FILE, " get_frame %.*s: count=%d", + (int)cport->name.slen, cport->name.ptr, + count)); + status = (cport->port->get_frame)(cport->port, &f); *type = f.type; @@ -997,6 +1019,9 @@ static pj_status_t read_port( pjmedia_conf *conf, f.buf = cport->rx_buf + cport->rx_buf_count; f.size = cport->samples_per_frame * BYTES_PER_SAMPLE; + TRACE_((THIS_FILE, " get_frame, count=%d", + cport->samples_per_frame)); + status = pjmedia_port_get_frame(cport->port, &f); if (status != PJ_SUCCESS) { @@ -1005,12 +1030,16 @@ static pj_status_t read_port( pjmedia_conf *conf, } if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) { + TRACE_((THIS_FILE, " get_frame returned non-audio")); zero_samples( cport->rx_buf + cport->rx_buf_count, cport->samples_per_frame); } cport->rx_buf_count += cport->samples_per_frame; + TRACE_((THIS_FILE, " rx buffer size is now %d", + cport->rx_buf_count)); + pj_assert(cport->rx_buf_count <= cport->rx_buf_cap); } @@ -1022,6 +1051,9 @@ static pj_status_t read_port( pjmedia_conf *conf, unsigned src_count; + TRACE_((THIS_FILE, " resample, input count=%d", + pjmedia_resample_get_input_size(cport->rx_resample))); + pjmedia_resample_run( cport->rx_resample,cport->rx_buf, frame); src_count = (unsigned)(count * 1.0 * cport->clock_rate / @@ -1032,6 +1064,9 @@ static pj_status_t read_port( pjmedia_conf *conf, cport->rx_buf_count); } + TRACE_((THIS_FILE, " rx buffer size is now %d", + cport->rx_buf_count)); + } else { copy_samples(frame, cport->rx_buf, count); @@ -1067,8 +1102,9 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, frame.buf = NULL; frame.size = 0; - if (cport->port && cport->port->put_frame) + if (cport->port && cport->port->put_frame) { pjmedia_port_put_frame(cport->port, &frame); + } cport->tx_level = 0; return PJ_SUCCESS; @@ -1111,11 +1147,15 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, buf[j] = (pj_int16_t) itemp; } - } else { + } else if (cport->sources) { /* No need to adjust signal level. */ for (j=0; jsamples_per_frame; ++j) { buf[j] = unsigned2pcm(cport->mix_buf[j] / cport->sources); } + } else { + // Not necessarry. Buffer has been zeroed before. + // zero_samples(buf, conf->samples_per_frame); + pj_assert(buf[0] == 0); } /* Calculate TX level if we need to do so. @@ -1154,6 +1194,10 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, frame.size = conf->samples_per_frame * BYTES_PER_SAMPLE; frame.timestamp.u64 = timestamp; + TRACE_((THIS_FILE, "put_frame %.*s, count=%d", + (int)cport->name.slen, cport->name.ptr, + frame.size / BYTES_PER_SAMPLE)); + return pjmedia_port_put_frame(cport->port, &frame); } else return PJ_SUCCESS; @@ -1185,6 +1229,10 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, pj_status_t status; + TRACE_((THIS_FILE, "write_port %.*s: count=%d", + (int)cport->name.slen, cport->name.ptr, + cport->samples_per_frame)); + if (cport->port) { pjmedia_frame frame; @@ -1193,6 +1241,10 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, frame.size = cport->samples_per_frame * BYTES_PER_SAMPLE; frame.timestamp.u64 = timestamp; + TRACE_((THIS_FILE, "put_frame %.*s, count=%d", + (int)cport->name.slen, cport->name.ptr, + frame.size / BYTES_PER_SAMPLE)); + status = pjmedia_port_put_frame(cport->port, &frame); } else @@ -1205,6 +1257,9 @@ static pj_status_t write_port(pjmedia_conf *conf, struct conf_port *cport, cport->tx_buf_count); } + TRACE_((THIS_FILE, " tx_buf count now is %d", + cport->tx_buf_count)); + return status; } @@ -1221,6 +1276,8 @@ static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_conf *conf = this_port->user_data; unsigned ci, cj, i, j; + TRACE_((THIS_FILE, "- clock -")); + /* Check that correct size is specified. */ pj_assert(frame->size == conf->samples_per_frame * conf->bits_per_sample / 8); @@ -1228,8 +1285,6 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* Must lock mutex (must we??) */ pj_mutex_lock(conf->mutex); - TRACE_(("p")); - /* Zero all port's temporary buffers. */ for (i=0, ci=0; imax_ports && ci < conf->port_cnt; ++i) { struct conf_port *conf_port = conf->ports[i]; @@ -1244,8 +1299,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, conf_port->sources = 0; mix_buf = conf_port->mix_buf; - for (j=0; jsamples_per_frame; ++j) - mix_buf[j] = 0; + for (j=0; jsamples_per_frame; ++j) mix_buf[j] = 0; } /* Get frames from all ports, and "mix" the signal @@ -1421,6 +1475,9 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* Return sound playback frame. */ if (conf->ports[0]->sources) { + TRACE_((THIS_FILE, "write to audio, count=%d", + conf->samples_per_frame)); + copy_samples( frame->buf, (pj_int16_t*)conf->ports[0]->mix_buf, conf->samples_per_frame); } else { @@ -1432,6 +1489,13 @@ static pj_status_t get_frame(pjmedia_port *this_port, pj_mutex_unlock(conf->mutex); +#ifdef REC_FILE + if (fhnd_rec == NULL) + fhnd_rec = fopen(REC_FILE, "wb"); + if (fhnd_rec) + fwrite(frame->buf, frame->size, 1, fhnd_rec); +#endif + return PJ_SUCCESS; } @@ -1448,8 +1512,6 @@ static pj_status_t put_frame(pjmedia_port *this_port, pj_int16_t *target_snd_buf; unsigned i; - TRACE_(("r")); - /* Check for correct size. */ PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame * conf->bits_per_sample / 8, diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c index 86c78d5f..e5759c1d 100644 --- a/pjmedia/src/pjmedia/errno.c +++ b/pjmedia/src/pjmedia/errno.c @@ -122,6 +122,7 @@ static const struct { PJMEDIA_ENOTVALIDWAVE, "Not a valid WAVE file" }, { PJMEDIA_EWAVEUNSUPP, "Unsupported WAVE file format" }, { PJMEDIA_EWAVETOOSHORT, "WAVE file too short" }, + { PJMEDIA_EFRMFILETOOBIG, "Sound frame too large for file buffer"}, /* Sound device errors: */ { PJMEDIA_ENOSNDREC, "No suitable sound capture device" }, diff --git a/pjmedia/src/pjmedia/file_player.c b/pjmedia/src/pjmedia/file_player.c new file mode 100644 index 00000000..ca5a3e1e --- /dev/null +++ b/pjmedia/src/pjmedia/file_player.c @@ -0,0 +1,396 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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. + * + * This program 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 +#include +#include +#include +#include +#include +#include +#include +#include + + +#define THIS_FILE "file_port.c" + + +#define SIGNATURE ('F'<<24|'P'<<16|'L'<<8|'Y') +#define BYTES_PER_SAMPLE 2 + + +#if 1 +# define TRACE_(x) PJ_LOG(4,x) +#else +# define TRACE_(x) +#endif + +#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0 + static void samples_to_host(pj_int16_t *samples, unsigned count) + { + unsigned i; + for (i=0; ibase.info.name = pj_str("file"); + port->base.info.signature = SIGNATURE; + port->base.info.type = PJMEDIA_TYPE_AUDIO; + port->base.info.has_info = PJ_TRUE; + port->base.info.need_info = PJ_FALSE; + port->base.info.pt = 0xFF; + port->base.info.encoding_name = pj_str("pcm"); + + port->base.put_frame = &file_put_frame; + port->base.get_frame = &file_get_frame; + port->base.on_destroy = &file_on_destroy; + + + /* Put in default values. + * These will be overriden once the file is read. + */ + port->base.info.sample_rate = 8000; + port->base.info.bits_per_sample = 16; + port->base.info.samples_per_frame = 160; + port->base.info.bytes_per_frame = 320; + + return port; +} + +/* + * Fill buffer. + */ +static pj_status_t fill_buffer(struct file_port *fport) +{ + pj_ssize_t size_left = fport->bufsize; + unsigned size_to_read; + pj_ssize_t size; + pj_status_t status; + + while (size_left > 0) { + + /* Calculate how many bytes to read in this run. */ + size = size_to_read = size_left; + status = pj_file_read(fport->fd, + &fport->buf[fport->bufsize-size_left], + &size); + if (status != PJ_SUCCESS) + return status; + if (size < 0) { + /* Should return more appropriate error code here.. */ + return PJ_ECANCELLED; + } + + size_left -= size; + fport->fpos += size; + + /* If size is less than size_to_read, it indicates that we've + * encountered EOF. Rewind the file. + */ + if (size < (pj_ssize_t)size_to_read) { + PJ_LOG(5,(THIS_FILE, "File port %.*s EOF, rewinding..", + (int)fport->base.info.name.slen, + fport->base.info.name.ptr)); + fport->fpos = sizeof(struct pjmedia_wave_hdr); + pj_file_setpos( fport->fd, fport->fpos, PJ_SEEK_SET); + } + } + + /* Convert samples to host rep */ + samples_to_host((pj_int16_t*)fport->buf, fport->bufsize/BYTES_PER_SAMPLE); + + return PJ_SUCCESS; +} + + +/* + * Create WAVE player port. + */ +PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool, + const char *filename, + unsigned flags, + pj_ssize_t buff_size, + void *user_data, + pjmedia_port **p_port ) +{ + pjmedia_wave_hdr wave_hdr; + pj_ssize_t size_read; + struct file_port *fport; + pj_status_t status; + + + PJ_UNUSED_ARG(flags); + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL); + + /* Check the file really exists. */ + if (!pj_file_exists(filename)) { + return PJ_ENOTFOUND; + } + + /* Create fport instance. */ + fport = create_file_port(pool); + if (!fport) { + return PJ_ENOMEM; + } + + + /* Get the file size. */ + fport->fsize = pj_file_size(filename); + + /* Size must be more than WAVE header size */ + if (fport->fsize <= sizeof(pjmedia_wave_hdr)) { + return PJMEDIA_ENOTVALIDWAVE; + } + + /* Open file. */ + status = pj_file_open( pool, filename, PJ_O_RDONLY, &fport->fd); + if (status != PJ_SUCCESS) + return status; + + /* Read the WAVE header. */ + size_read = sizeof(wave_hdr); + status = pj_file_read( fport->fd, &wave_hdr, &size_read); + if (status != PJ_SUCCESS) { + pj_file_close(fport->fd); + return status; + } + if (size_read != sizeof(wave_hdr)) { + pj_file_close(fport->fd); + return PJMEDIA_ENOTVALIDWAVE; + } + + /* Normalize WAVE header fields values from little-endian to host + * byte order. + */ + pjmedia_wave_hdr_file_to_host(&wave_hdr); + + /* Validate WAVE file. */ + if (wave_hdr.riff_hdr.riff != PJMEDIA_RIFF_TAG || + wave_hdr.riff_hdr.wave != PJMEDIA_WAVE_TAG || + wave_hdr.fmt_hdr.fmt != PJMEDIA_FMT_TAG) + { + pj_file_close(fport->fd); + TRACE_((THIS_FILE, + "actual value|expected riff=%x|%x, wave=%x|%x fmt=%x|%x", + wave_hdr.riff_hdr.riff, PJMEDIA_RIFF_TAG, + wave_hdr.riff_hdr.wave, PJMEDIA_WAVE_TAG, + wave_hdr.fmt_hdr.fmt, PJMEDIA_FMT_TAG)); + return PJMEDIA_ENOTVALIDWAVE; + } + + /* Must be PCM with 16bits per sample */ + if (wave_hdr.fmt_hdr.fmt_tag != 1 || + wave_hdr.fmt_hdr.bits_per_sample != 16) + { + pj_file_close(fport->fd); + return PJMEDIA_EWAVEUNSUPP; + } + + /* Block align must be 2*nchannels */ + if (wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan*BYTES_PER_SAMPLE) { + pj_file_close(fport->fd); + return PJMEDIA_EWAVEUNSUPP; + } + + /* Validate length. */ + if (wave_hdr.data_hdr.len != fport->fsize-sizeof(pjmedia_wave_hdr)) { + pj_file_close(fport->fd); + return PJMEDIA_EWAVEUNSUPP; + } + if (wave_hdr.data_hdr.len < 400) { + pj_file_close(fport->fd); + return PJMEDIA_EWAVETOOSHORT; + } + + /* It seems like we have a valid WAVE file. */ + + /* Initialize */ + fport->base.user_data = user_data; + + /* Update port info. */ + fport->base.info.channel_count = wave_hdr.fmt_hdr.nchan; + fport->base.info.sample_rate = wave_hdr.fmt_hdr.sample_rate; + fport->base.info.bits_per_sample = wave_hdr.fmt_hdr.bits_per_sample; + fport->base.info.samples_per_frame = fport->base.info.sample_rate * + wave_hdr.fmt_hdr.nchan * + 20 / 1000; + fport->base.info.bytes_per_frame = + fport->base.info.samples_per_frame * + fport->base.info.bits_per_sample / 8; + + pj_strdup2(pool, &fport->base.info.name, filename); + + /* Create file buffer. + */ + if (buff_size < 1) buff_size = PJMEDIA_FILE_PORT_BUFSIZE; + fport->bufsize = buff_size; + + + /* Create buffer. */ + fport->buf = pj_pool_alloc(pool, fport->bufsize); + if (!fport->buf) { + pj_file_close(fport->fd); + return PJ_ENOMEM; + } + + fport->readpos = fport->buf; + + /* Set initial position of the file. */ + fport->fpos = sizeof(struct pjmedia_wave_hdr); + + /* Fill up the buffer. */ + status = fill_buffer(fport); + if (status != PJ_SUCCESS) { + pj_file_close(fport->fd); + return status; + } + + /* Done. */ + + *p_port = &fport->base; + + + PJ_LOG(4,(THIS_FILE, + "File player '%.*s' created: samp.rate=%d, ch=%d, bufsize=%uKB, " + "filesize=%luKB", + (int)fport->base.info.name.slen, + fport->base.info.name.ptr, + fport->base.info.sample_rate, + fport->base.info.channel_count, + fport->bufsize / 1000, + (unsigned long)(fport->fsize / 1000))); + + return PJ_SUCCESS; +} + + +/* + * Put frame to file. + */ +static pj_status_t file_put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame) +{ + PJ_UNUSED_ARG(this_port); + PJ_UNUSED_ARG(frame); + return PJ_EINVALIDOP; +} + +/* + * Get frame from file. + */ +static pj_status_t file_get_frame(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + struct file_port *fport = (struct file_port*)this_port; + unsigned frame_size; + pj_status_t status; + + pj_assert(fport->base.info.signature == SIGNATURE); + + //frame_size = fport->base.info.bytes_per_frame; + //pj_assert(frame->size == frame_size); + frame_size = frame->size; + + /* Copy frame from buffer. */ + frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + frame->size = frame_size; + frame->timestamp.u64 = 0; + + if (fport->readpos + frame_size <= fport->buf + fport->bufsize) { + + /* Read contiguous buffer. */ + pj_memcpy(frame->buf, fport->readpos, frame_size); + + /* Fill up the buffer if all has been read. */ + fport->readpos += frame_size; + if (fport->readpos == fport->buf + fport->bufsize) { + fport->readpos = fport->buf; + + status = fill_buffer(fport); + if (status != PJ_SUCCESS) + return status; + } + } else { + unsigned endread; + + /* Split read. + * First stage: read until end of buffer. + */ + endread = (fport->buf+fport->bufsize) - fport->readpos; + pj_memcpy(frame->buf, fport->readpos, endread); + + /* Second stage: fill up buffer, and read from the start of buffer. */ + status = fill_buffer(fport); + if (status != PJ_SUCCESS) { + pj_memset(((char*)frame->buf)+endread, 0, frame_size-endread); + return status; + } + + pj_memcpy(((char*)frame->buf)+endread, fport->buf, frame_size-endread); + fport->readpos = fport->buf + (frame_size - endread); + } + + return PJ_SUCCESS; +} + +/* + * Destroy port. + */ +static pj_status_t file_on_destroy(pjmedia_port *this_port) +{ + struct file_port *fport = (struct file_port*) this_port; + + pj_assert(this_port->info.signature == SIGNATURE); + + pj_file_close(fport->fd); + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia/file_port.c b/pjmedia/src/pjmedia/file_port.c deleted file mode 100644 index f1b2e819..00000000 --- a/pjmedia/src/pjmedia/file_port.c +++ /dev/null @@ -1,448 +0,0 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * This program 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. - * - * This program 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 -#include -#include -#include -#include -#include -#include -#include -#include - - -#define THIS_FILE "file_port.c" - - -#ifndef PJMEDIA_FILE_PORT_BUFSIZE -# define PJMEDIA_FILE_PORT_BUFSIZE 4000 -#endif - - -#define SIGNATURE ('F'<<24|'I'<<16|'L'<<8|'E') - -#if 1 -# define TRACE_(x) PJ_LOG(4,x) -#else -# define TRACE_(x) -#endif - -#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0 - PJ_INLINE(pj_int16_t) swap16(pj_int16_t val) - { - pj_uint8_t *p = (pj_uint8_t*)&val; - pj_uint8_t tmp = *p; - *p = *(p+1); - *(p+1) = tmp; - return val; - } - PJ_INLINE(pj_int32_t) swap32(pj_int32_t val) - { - pj_uint8_t *p = (pj_uint8_t*)&val; - pj_uint8_t tmp = *p; - *p = *(p+3); - *(p+3) = tmp; - tmp = *(p+1); - *(p+1) = *(p+2); - *(p+2) = tmp; - return val; - } -# define SWAP16(val16) swap16(val16) -# define SWAP32(val32) swap32(val32) - static void samples_to_host(pj_int16_t *samples, unsigned count) - { - unsigned i; - for (i=0; ibase.info.name = pj_str("file"); - port->base.info.signature = SIGNATURE; - port->base.info.type = PJMEDIA_TYPE_AUDIO; - port->base.info.has_info = PJ_TRUE; - port->base.info.need_info = PJ_FALSE; - port->base.info.pt = 0xFF; - port->base.info.encoding_name = pj_str("pcm"); - - port->base.put_frame = &file_put_frame; - port->base.get_frame = &file_get_frame; - port->base.on_destroy = &file_on_destroy; - - - /* Put in default values. - * These will be overriden once the file is read. - */ - port->base.info.sample_rate = 8000; - port->base.info.bits_per_sample = 16; - port->base.info.samples_per_frame = 160; - port->base.info.bytes_per_frame = 320; - - return port; -} - -/* - * Fill buffer. - */ -static pj_status_t fill_buffer(struct file_port *fport) -{ - pj_ssize_t size_left = fport->bufsize; - unsigned size_to_read; - pj_ssize_t size; - pj_status_t status; - - while (size_left > 0) { - - /* Calculate how many bytes to read in this run. */ - size = size_to_read = size_left; - status = pj_file_read(fport->fd, - &fport->buf[fport->bufsize-size_left], - &size); - if (status != PJ_SUCCESS) - return status; - if (size < 0) { - /* Should return more appropriate error code here.. */ - return PJ_ECANCELLED; - } - - size_left -= size; - fport->fpos += size; - - /* If size is less than size_to_read, it indicates that we've - * encountered EOF. Rewind the file. - */ - if (size < (pj_ssize_t)size_to_read) { - PJ_LOG(5,(THIS_FILE, "File port %.*s EOF, rewinding..", - (int)fport->base.info.name.slen, - fport->base.info.name.ptr)); - fport->fpos = sizeof(struct pjmedia_wave_hdr); - pj_file_setpos( fport->fd, fport->fpos, PJ_SEEK_SET); - } - } - - /* Convert samples to host rep */ - samples_to_host((pj_int16_t*)fport->buf, fport->bufsize/2); - - return PJ_SUCCESS; -} - - -/* - * Change the endianness of WAVE header fields. - */ -void pjmedia_wave_hdr_swap_bytes( pjmedia_wave_hdr *hdr ) -{ - hdr->riff_hdr.riff = SWAP32(hdr->riff_hdr.riff); - hdr->riff_hdr.file_len = SWAP32(hdr->riff_hdr.file_len); - hdr->riff_hdr.wave = SWAP32(hdr->riff_hdr.wave); - - hdr->fmt_hdr.fmt = SWAP32(hdr->fmt_hdr.fmt); - hdr->fmt_hdr.len = SWAP32(hdr->fmt_hdr.len); - hdr->fmt_hdr.fmt_tag = SWAP16(hdr->fmt_hdr.fmt_tag); - hdr->fmt_hdr.nchan = SWAP16(hdr->fmt_hdr.nchan); - hdr->fmt_hdr.sample_rate = SWAP32(hdr->fmt_hdr.sample_rate); - hdr->fmt_hdr.bytes_per_sec = SWAP32(hdr->fmt_hdr.bytes_per_sec); - hdr->fmt_hdr.block_align = SWAP16(hdr->fmt_hdr.block_align); - hdr->fmt_hdr.bits_per_sample = SWAP16(hdr->fmt_hdr.bits_per_sample); - - hdr->data_hdr.data = SWAP32(hdr->data_hdr.data); - hdr->data_hdr.len = SWAP32(hdr->data_hdr.len); -} - - -#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0 -# define normalize_wave_hdr(hdr) pjmedia_wave_hdr_swap_bytes(hdr) -#else -# define normalize_wave_hdr(hdr) -#endif - - -/* - * Create WAVE player port. - */ -PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool, - const char *filename, - unsigned flags, - pj_ssize_t buff_size, - void *user_data, - pjmedia_port **p_port ) -{ - pjmedia_wave_hdr wave_hdr; - pj_ssize_t size_read; - struct file_port *fport; - pj_status_t status; - - - PJ_UNUSED_ARG(flags); - PJ_UNUSED_ARG(buff_size); - - /* Check arguments. */ - PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL); - - /* Check the file really exists. */ - if (!pj_file_exists(filename)) { - return PJ_ENOTFOUND; - } - - /* Create fport instance. */ - fport = create_file_port(pool); - if (!fport) { - return PJ_ENOMEM; - } - - - /* Get the file size. */ - fport->fsize = pj_file_size(filename); - - /* Size must be more than WAVE header size */ - if (fport->fsize <= sizeof(pjmedia_wave_hdr)) { - return PJMEDIA_ENOTVALIDWAVE; - } - - /* Open file. */ - status = pj_file_open( pool, filename, PJ_O_RDONLY, &fport->fd); - if (status != PJ_SUCCESS) - return status; - - /* Read the WAVE header. */ - size_read = sizeof(wave_hdr); - status = pj_file_read( fport->fd, &wave_hdr, &size_read); - if (status != PJ_SUCCESS) { - pj_file_close(fport->fd); - return status; - } - if (size_read != sizeof(wave_hdr)) { - pj_file_close(fport->fd); - return PJMEDIA_ENOTVALIDWAVE; - } - - /* Normalize WAVE header fields value (only used in big-endian hosts) */ - normalize_wave_hdr(&wave_hdr); - - /* Validate WAVE file. */ - if (wave_hdr.riff_hdr.riff != PJMEDIA_RIFF_TAG || - wave_hdr.riff_hdr.wave != PJMEDIA_WAVE_TAG || - wave_hdr.fmt_hdr.fmt != PJMEDIA_FMT_TAG) - { - pj_file_close(fport->fd); - TRACE_((THIS_FILE, - "actual value|expected riff=%x|%x, wave=%x|%x fmt=%x|%x", - wave_hdr.riff_hdr.riff, PJMEDIA_RIFF_TAG, - wave_hdr.riff_hdr.wave, PJMEDIA_WAVE_TAG, - wave_hdr.fmt_hdr.fmt, PJMEDIA_FMT_TAG)); - return PJMEDIA_ENOTVALIDWAVE; - } - - /* Must be PCM with 16bits per sample */ - if (wave_hdr.fmt_hdr.fmt_tag != 1 || - wave_hdr.fmt_hdr.bits_per_sample != 16) - { - pj_file_close(fport->fd); - return PJMEDIA_EWAVEUNSUPP; - } - - /* Block align must be 2*nchannels */ - if (wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan*2) { - pj_file_close(fport->fd); - return PJMEDIA_EWAVEUNSUPP; - } - - /* Validate length. */ - if (wave_hdr.data_hdr.len != fport->fsize-sizeof(pjmedia_wave_hdr)) { - pj_file_close(fport->fd); - return PJMEDIA_EWAVEUNSUPP; - } - if (wave_hdr.data_hdr.len < 400) { - pj_file_close(fport->fd); - return PJMEDIA_EWAVETOOSHORT; - } - - /* It seems like we have a valid WAVE file. */ - - /* Initialize */ - fport->base.user_data = user_data; - - /* Update port info. */ - fport->base.info.channel_count = wave_hdr.fmt_hdr.nchan; - fport->base.info.sample_rate = wave_hdr.fmt_hdr.sample_rate; - fport->base.info.bits_per_sample = wave_hdr.fmt_hdr.bits_per_sample; - fport->base.info.samples_per_frame = fport->base.info.sample_rate * - wave_hdr.fmt_hdr.nchan * - 20 / 1000; - fport->base.info.bytes_per_frame = - fport->base.info.samples_per_frame * - fport->base.info.bits_per_sample / 8; - - pj_strdup2(pool, &fport->base.info.name, filename); - - /* Create file buffer. - */ - fport->bufsize = PJMEDIA_FILE_PORT_BUFSIZE; - - - /* Create buffer. */ - fport->buf = pj_pool_alloc(pool, fport->bufsize); - if (!fport->buf) { - pj_file_close(fport->fd); - return PJ_ENOMEM; - } - - fport->readpos = fport->buf; - - /* Set initial position of the file. */ - fport->fpos = sizeof(struct pjmedia_wave_hdr); - - /* Fill up the buffer. */ - status = fill_buffer(fport); - if (status != PJ_SUCCESS) { - pj_file_close(fport->fd); - return status; - } - - /* Done. */ - - *p_port = &fport->base; - - - PJ_LOG(4,(THIS_FILE, - "File port '%.*s' created: clock=%dKHz, bufsize=%uKB, " - "filesize=%luKB", - (int)fport->base.info.name.slen, - fport->base.info.name.ptr, - fport->base.info.sample_rate/1000, - fport->bufsize / 1000, - (unsigned long)(fport->fsize / 1000))); - - return PJ_SUCCESS; -} - - -/* - * Put frame to file. - */ -static pj_status_t file_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame) -{ - PJ_UNUSED_ARG(this_port); - PJ_UNUSED_ARG(frame); - return PJ_EINVALIDOP; -} - -/* - * Get frame from file. - */ -static pj_status_t file_get_frame(pjmedia_port *this_port, - pjmedia_frame *frame) -{ - struct file_port *fport = (struct file_port*)this_port; - unsigned frame_size; - pj_status_t status; - - pj_assert(fport->base.info.signature == SIGNATURE); - - frame_size = fport->base.info.bytes_per_frame; - pj_assert(frame->size == frame_size); - - /* Copy frame from buffer. */ - frame->type = PJMEDIA_FRAME_TYPE_AUDIO; - frame->size = frame_size; - frame->timestamp.u64 = 0; - - if (fport->readpos + frame_size <= fport->buf + fport->bufsize) { - - /* Read contiguous buffer. */ - pj_memcpy(frame->buf, fport->readpos, frame_size); - - /* Fill up the buffer if all has been read. */ - fport->readpos += frame_size; - if (fport->readpos == fport->buf + fport->bufsize) { - fport->readpos = fport->buf; - - status = fill_buffer(fport); - if (status != PJ_SUCCESS) - return status; - } - } else { - unsigned endread; - - /* Split read. - * First stage: read until end of buffer. - */ - endread = (fport->buf+fport->bufsize) - fport->readpos; - pj_memcpy(frame->buf, fport->readpos, endread); - - /* Second stage: fill up buffer, and read from the start of buffer. */ - status = fill_buffer(fport); - if (status != PJ_SUCCESS) { - pj_memset(((char*)frame->buf)+endread, 0, frame_size-endread); - return status; - } - - pj_memcpy(((char*)frame->buf)+endread, fport->buf, frame_size-endread); - fport->readpos = fport->buf + (frame_size - endread); - } - - return PJ_SUCCESS; -} - -/* - * Destroy port. - */ -static pj_status_t file_on_destroy(pjmedia_port *this_port) -{ - struct file_port *fport = (struct file_port*) this_port; - - pj_assert(this_port->info.signature == SIGNATURE); - - pj_file_close(fport->fd); - return PJ_SUCCESS; -} diff --git a/pjmedia/src/pjmedia/file_reader.c b/pjmedia/src/pjmedia/file_reader.c deleted file mode 100644 index f1b2e819..00000000 --- a/pjmedia/src/pjmedia/file_reader.c +++ /dev/null @@ -1,448 +0,0 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * This program 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. - * - * This program 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 -#include -#include -#include -#include -#include -#include -#include -#include - - -#define THIS_FILE "file_port.c" - - -#ifndef PJMEDIA_FILE_PORT_BUFSIZE -# define PJMEDIA_FILE_PORT_BUFSIZE 4000 -#endif - - -#define SIGNATURE ('F'<<24|'I'<<16|'L'<<8|'E') - -#if 1 -# define TRACE_(x) PJ_LOG(4,x) -#else -# define TRACE_(x) -#endif - -#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0 - PJ_INLINE(pj_int16_t) swap16(pj_int16_t val) - { - pj_uint8_t *p = (pj_uint8_t*)&val; - pj_uint8_t tmp = *p; - *p = *(p+1); - *(p+1) = tmp; - return val; - } - PJ_INLINE(pj_int32_t) swap32(pj_int32_t val) - { - pj_uint8_t *p = (pj_uint8_t*)&val; - pj_uint8_t tmp = *p; - *p = *(p+3); - *(p+3) = tmp; - tmp = *(p+1); - *(p+1) = *(p+2); - *(p+2) = tmp; - return val; - } -# define SWAP16(val16) swap16(val16) -# define SWAP32(val32) swap32(val32) - static void samples_to_host(pj_int16_t *samples, unsigned count) - { - unsigned i; - for (i=0; ibase.info.name = pj_str("file"); - port->base.info.signature = SIGNATURE; - port->base.info.type = PJMEDIA_TYPE_AUDIO; - port->base.info.has_info = PJ_TRUE; - port->base.info.need_info = PJ_FALSE; - port->base.info.pt = 0xFF; - port->base.info.encoding_name = pj_str("pcm"); - - port->base.put_frame = &file_put_frame; - port->base.get_frame = &file_get_frame; - port->base.on_destroy = &file_on_destroy; - - - /* Put in default values. - * These will be overriden once the file is read. - */ - port->base.info.sample_rate = 8000; - port->base.info.bits_per_sample = 16; - port->base.info.samples_per_frame = 160; - port->base.info.bytes_per_frame = 320; - - return port; -} - -/* - * Fill buffer. - */ -static pj_status_t fill_buffer(struct file_port *fport) -{ - pj_ssize_t size_left = fport->bufsize; - unsigned size_to_read; - pj_ssize_t size; - pj_status_t status; - - while (size_left > 0) { - - /* Calculate how many bytes to read in this run. */ - size = size_to_read = size_left; - status = pj_file_read(fport->fd, - &fport->buf[fport->bufsize-size_left], - &size); - if (status != PJ_SUCCESS) - return status; - if (size < 0) { - /* Should return more appropriate error code here.. */ - return PJ_ECANCELLED; - } - - size_left -= size; - fport->fpos += size; - - /* If size is less than size_to_read, it indicates that we've - * encountered EOF. Rewind the file. - */ - if (size < (pj_ssize_t)size_to_read) { - PJ_LOG(5,(THIS_FILE, "File port %.*s EOF, rewinding..", - (int)fport->base.info.name.slen, - fport->base.info.name.ptr)); - fport->fpos = sizeof(struct pjmedia_wave_hdr); - pj_file_setpos( fport->fd, fport->fpos, PJ_SEEK_SET); - } - } - - /* Convert samples to host rep */ - samples_to_host((pj_int16_t*)fport->buf, fport->bufsize/2); - - return PJ_SUCCESS; -} - - -/* - * Change the endianness of WAVE header fields. - */ -void pjmedia_wave_hdr_swap_bytes( pjmedia_wave_hdr *hdr ) -{ - hdr->riff_hdr.riff = SWAP32(hdr->riff_hdr.riff); - hdr->riff_hdr.file_len = SWAP32(hdr->riff_hdr.file_len); - hdr->riff_hdr.wave = SWAP32(hdr->riff_hdr.wave); - - hdr->fmt_hdr.fmt = SWAP32(hdr->fmt_hdr.fmt); - hdr->fmt_hdr.len = SWAP32(hdr->fmt_hdr.len); - hdr->fmt_hdr.fmt_tag = SWAP16(hdr->fmt_hdr.fmt_tag); - hdr->fmt_hdr.nchan = SWAP16(hdr->fmt_hdr.nchan); - hdr->fmt_hdr.sample_rate = SWAP32(hdr->fmt_hdr.sample_rate); - hdr->fmt_hdr.bytes_per_sec = SWAP32(hdr->fmt_hdr.bytes_per_sec); - hdr->fmt_hdr.block_align = SWAP16(hdr->fmt_hdr.block_align); - hdr->fmt_hdr.bits_per_sample = SWAP16(hdr->fmt_hdr.bits_per_sample); - - hdr->data_hdr.data = SWAP32(hdr->data_hdr.data); - hdr->data_hdr.len = SWAP32(hdr->data_hdr.len); -} - - -#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0 -# define normalize_wave_hdr(hdr) pjmedia_wave_hdr_swap_bytes(hdr) -#else -# define normalize_wave_hdr(hdr) -#endif - - -/* - * Create WAVE player port. - */ -PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool, - const char *filename, - unsigned flags, - pj_ssize_t buff_size, - void *user_data, - pjmedia_port **p_port ) -{ - pjmedia_wave_hdr wave_hdr; - pj_ssize_t size_read; - struct file_port *fport; - pj_status_t status; - - - PJ_UNUSED_ARG(flags); - PJ_UNUSED_ARG(buff_size); - - /* Check arguments. */ - PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL); - - /* Check the file really exists. */ - if (!pj_file_exists(filename)) { - return PJ_ENOTFOUND; - } - - /* Create fport instance. */ - fport = create_file_port(pool); - if (!fport) { - return PJ_ENOMEM; - } - - - /* Get the file size. */ - fport->fsize = pj_file_size(filename); - - /* Size must be more than WAVE header size */ - if (fport->fsize <= sizeof(pjmedia_wave_hdr)) { - return PJMEDIA_ENOTVALIDWAVE; - } - - /* Open file. */ - status = pj_file_open( pool, filename, PJ_O_RDONLY, &fport->fd); - if (status != PJ_SUCCESS) - return status; - - /* Read the WAVE header. */ - size_read = sizeof(wave_hdr); - status = pj_file_read( fport->fd, &wave_hdr, &size_read); - if (status != PJ_SUCCESS) { - pj_file_close(fport->fd); - return status; - } - if (size_read != sizeof(wave_hdr)) { - pj_file_close(fport->fd); - return PJMEDIA_ENOTVALIDWAVE; - } - - /* Normalize WAVE header fields value (only used in big-endian hosts) */ - normalize_wave_hdr(&wave_hdr); - - /* Validate WAVE file. */ - if (wave_hdr.riff_hdr.riff != PJMEDIA_RIFF_TAG || - wave_hdr.riff_hdr.wave != PJMEDIA_WAVE_TAG || - wave_hdr.fmt_hdr.fmt != PJMEDIA_FMT_TAG) - { - pj_file_close(fport->fd); - TRACE_((THIS_FILE, - "actual value|expected riff=%x|%x, wave=%x|%x fmt=%x|%x", - wave_hdr.riff_hdr.riff, PJMEDIA_RIFF_TAG, - wave_hdr.riff_hdr.wave, PJMEDIA_WAVE_TAG, - wave_hdr.fmt_hdr.fmt, PJMEDIA_FMT_TAG)); - return PJMEDIA_ENOTVALIDWAVE; - } - - /* Must be PCM with 16bits per sample */ - if (wave_hdr.fmt_hdr.fmt_tag != 1 || - wave_hdr.fmt_hdr.bits_per_sample != 16) - { - pj_file_close(fport->fd); - return PJMEDIA_EWAVEUNSUPP; - } - - /* Block align must be 2*nchannels */ - if (wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan*2) { - pj_file_close(fport->fd); - return PJMEDIA_EWAVEUNSUPP; - } - - /* Validate length. */ - if (wave_hdr.data_hdr.len != fport->fsize-sizeof(pjmedia_wave_hdr)) { - pj_file_close(fport->fd); - return PJMEDIA_EWAVEUNSUPP; - } - if (wave_hdr.data_hdr.len < 400) { - pj_file_close(fport->fd); - return PJMEDIA_EWAVETOOSHORT; - } - - /* It seems like we have a valid WAVE file. */ - - /* Initialize */ - fport->base.user_data = user_data; - - /* Update port info. */ - fport->base.info.channel_count = wave_hdr.fmt_hdr.nchan; - fport->base.info.sample_rate = wave_hdr.fmt_hdr.sample_rate; - fport->base.info.bits_per_sample = wave_hdr.fmt_hdr.bits_per_sample; - fport->base.info.samples_per_frame = fport->base.info.sample_rate * - wave_hdr.fmt_hdr.nchan * - 20 / 1000; - fport->base.info.bytes_per_frame = - fport->base.info.samples_per_frame * - fport->base.info.bits_per_sample / 8; - - pj_strdup2(pool, &fport->base.info.name, filename); - - /* Create file buffer. - */ - fport->bufsize = PJMEDIA_FILE_PORT_BUFSIZE; - - - /* Create buffer. */ - fport->buf = pj_pool_alloc(pool, fport->bufsize); - if (!fport->buf) { - pj_file_close(fport->fd); - return PJ_ENOMEM; - } - - fport->readpos = fport->buf; - - /* Set initial position of the file. */ - fport->fpos = sizeof(struct pjmedia_wave_hdr); - - /* Fill up the buffer. */ - status = fill_buffer(fport); - if (status != PJ_SUCCESS) { - pj_file_close(fport->fd); - return status; - } - - /* Done. */ - - *p_port = &fport->base; - - - PJ_LOG(4,(THIS_FILE, - "File port '%.*s' created: clock=%dKHz, bufsize=%uKB, " - "filesize=%luKB", - (int)fport->base.info.name.slen, - fport->base.info.name.ptr, - fport->base.info.sample_rate/1000, - fport->bufsize / 1000, - (unsigned long)(fport->fsize / 1000))); - - return PJ_SUCCESS; -} - - -/* - * Put frame to file. - */ -static pj_status_t file_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame) -{ - PJ_UNUSED_ARG(this_port); - PJ_UNUSED_ARG(frame); - return PJ_EINVALIDOP; -} - -/* - * Get frame from file. - */ -static pj_status_t file_get_frame(pjmedia_port *this_port, - pjmedia_frame *frame) -{ - struct file_port *fport = (struct file_port*)this_port; - unsigned frame_size; - pj_status_t status; - - pj_assert(fport->base.info.signature == SIGNATURE); - - frame_size = fport->base.info.bytes_per_frame; - pj_assert(frame->size == frame_size); - - /* Copy frame from buffer. */ - frame->type = PJMEDIA_FRAME_TYPE_AUDIO; - frame->size = frame_size; - frame->timestamp.u64 = 0; - - if (fport->readpos + frame_size <= fport->buf + fport->bufsize) { - - /* Read contiguous buffer. */ - pj_memcpy(frame->buf, fport->readpos, frame_size); - - /* Fill up the buffer if all has been read. */ - fport->readpos += frame_size; - if (fport->readpos == fport->buf + fport->bufsize) { - fport->readpos = fport->buf; - - status = fill_buffer(fport); - if (status != PJ_SUCCESS) - return status; - } - } else { - unsigned endread; - - /* Split read. - * First stage: read until end of buffer. - */ - endread = (fport->buf+fport->bufsize) - fport->readpos; - pj_memcpy(frame->buf, fport->readpos, endread); - - /* Second stage: fill up buffer, and read from the start of buffer. */ - status = fill_buffer(fport); - if (status != PJ_SUCCESS) { - pj_memset(((char*)frame->buf)+endread, 0, frame_size-endread); - return status; - } - - pj_memcpy(((char*)frame->buf)+endread, fport->buf, frame_size-endread); - fport->readpos = fport->buf + (frame_size - endread); - } - - return PJ_SUCCESS; -} - -/* - * Destroy port. - */ -static pj_status_t file_on_destroy(pjmedia_port *this_port) -{ - struct file_port *fport = (struct file_port*) this_port; - - pj_assert(this_port->info.signature == SIGNATURE); - - pj_file_close(fport->fd); - return PJ_SUCCESS; -} diff --git a/pjmedia/src/pjmedia/file_writer.c b/pjmedia/src/pjmedia/file_writer.c new file mode 100644 index 00000000..8471ba48 --- /dev/null +++ b/pjmedia/src/pjmedia/file_writer.c @@ -0,0 +1,306 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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. + * + * This program 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 +#include +#include +#include +#include +#include +#include +#include +#include + + +#define THIS_FILE "file_writer.c" +#define SIGNATURE ('F'<<24|'W'<<16|'R'<<8|'T') +#define BYTES_PER_SAMPLE 2 + + +struct file_port +{ + pjmedia_port base; + pj_size_t bufsize; + char *buf; + char *writepos; + + pj_oshandle_t fd; +}; + +static pj_status_t file_put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame); +static pj_status_t file_get_frame(pjmedia_port *this_port, + pjmedia_frame *frame); +static pj_status_t file_on_destroy(pjmedia_port *this_port); + + +/* + * Create file writer port. + */ +PJ_DEF(pj_status_t) pjmedia_file_writer_port_create( pj_pool_t *pool, + const char *filename, + unsigned sampling_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + unsigned flags, + pj_ssize_t buff_size, + void *user_data, + pjmedia_port **p_port ) +{ + struct file_port *fport; + pjmedia_wave_hdr wave_hdr; + pj_ssize_t size; + pj_status_t status; + + PJ_UNUSED_ARG(flags); + PJ_UNUSED_ARG(user_data); + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL); + + /* Only supports 16bits per sample for now. + * See flush_buffer(). + */ + PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); + + /* Create file port instance. */ + fport = pj_pool_zalloc(pool, sizeof(struct file_port)); + PJ_ASSERT_RETURN(fport != NULL, PJ_ENOMEM); + + /* Initialize port info. */ + fport->base.info.bits_per_sample = bits_per_sample; + fport->base.info.bytes_per_frame = samples_per_frame * bits_per_sample * + channel_count / 8; + fport->base.info.channel_count = channel_count; + fport->base.info.encoding_name = pj_str("pom"); + fport->base.info.has_info = 1; + pj_strdup2(pool, &fport->base.info.name, filename); + fport->base.info.need_info = 0; + fport->base.info.pt = 0xFF; + fport->base.info.sample_rate = sampling_rate; + fport->base.info.samples_per_frame = samples_per_frame; + fport->base.info.signature = SIGNATURE; + fport->base.info.type = PJMEDIA_TYPE_AUDIO; + + fport->base.get_frame = &file_get_frame; + fport->base.put_frame = &file_put_frame; + fport->base.on_destroy = &file_on_destroy; + + + /* Open file in write and read mode. + * We need the read mode because we'll modify the WAVE header once + * the recording has completed. + */ + status = pj_file_open(pool, filename, PJ_O_WRONLY, &fport->fd); + if (status != PJ_SUCCESS) + return status; + + /* Initialize WAVE header */ + pj_memset(&wave_hdr, 0, sizeof(pjmedia_wave_hdr)); + wave_hdr.riff_hdr.riff = PJMEDIA_RIFF_TAG; + wave_hdr.riff_hdr.file_len = 0; /* will be filled later */ + wave_hdr.riff_hdr.wave = PJMEDIA_WAVE_TAG; + + wave_hdr.fmt_hdr.fmt = PJMEDIA_FMT_TAG; + wave_hdr.fmt_hdr.len = 16; + wave_hdr.fmt_hdr.fmt_tag = 1; + wave_hdr.fmt_hdr.nchan = (pj_int16_t)channel_count; + wave_hdr.fmt_hdr.sample_rate = sampling_rate; + wave_hdr.fmt_hdr.bytes_per_sec = sampling_rate * channel_count * + bits_per_sample / 8; + wave_hdr.fmt_hdr.block_align = (pj_int16_t) (channel_count * + bits_per_sample / 8); + wave_hdr.fmt_hdr.bits_per_sample = (pj_int16_t)bits_per_sample; + + wave_hdr.data_hdr.data = PJMEDIA_DATA_TAG; + wave_hdr.data_hdr.len = 0; /* will be filled later */ + + + /* Convert WAVE header from host byte order to little endian + * before writing the header. + */ + pjmedia_wave_hdr_host_to_file(&wave_hdr); + + + /* Write WAVE header */ + size = sizeof(pjmedia_wave_hdr); + status = pj_file_write(fport->fd, &wave_hdr, &size); + if (status != PJ_SUCCESS) { + pj_file_close(fport->fd); + return status; + } + + /* Set buffer size. */ + if (buff_size < 1) buff_size = PJMEDIA_FILE_PORT_BUFSIZE; + fport->bufsize = buff_size; + + /* Check that buffer size is greater than bytes per frame */ + pj_assert(fport->bufsize >= fport->base.info.bytes_per_frame); + + + /* Allocate buffer and set initial write position */ + fport->buf = pj_pool_alloc(pool, fport->bufsize); + if (fport->buf == NULL) { + pj_file_close(fport->fd); + return PJ_ENOMEM; + } + fport->writepos = fport->buf; + + /* Done. */ + *p_port = &fport->base; + + PJ_LOG(4,(THIS_FILE, + "File writer '%.*s' created: samp.rate=%d, bufsize=%uKB", + (int)fport->base.info.name.slen, + fport->base.info.name.ptr, + fport->base.info.sample_rate, + fport->bufsize / 1000)); + + + return PJ_SUCCESS; +} + + +#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0 + static void swap_samples(pj_int16_t *samples, unsigned count) + { + unsigned i; + for (i=0; iwritepos - fport->buf; + pj_status_t status; + + /* Convert samples to little endian */ + swap_samples((pj_int16_t*)fport->buf, bytes/BYTES_PER_SAMPLE); + + /* Write to file. */ + status = pj_file_write(fport->fd, fport->buf, &bytes); + + /* Reset writepos */ + fport->writepos = fport->buf; + + return status; +} + +/* + * Put a frame into the buffer. When the buffer is full, flush the buffer + * to the file. + */ +static pj_status_t file_put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame) +{ + struct file_port *fport = (struct file_port *)this_port; + + /* Flush buffer if we don't have enough room for the frame. */ + if (fport->writepos + frame->size > fport->buf + fport->bufsize) { + pj_status_t status; + status = flush_buffer(fport); + if (status != PJ_SUCCESS) + return status; + } + + /* Check if frame is not too large. */ + PJ_ASSERT_RETURN(fport->writepos+frame->size <= fport->buf+fport->bufsize, + PJMEDIA_EFRMFILETOOBIG); + + /* Copy frame to buffer. */ + pj_memcpy(fport->writepos, frame->buf, frame->size); + fport->writepos += frame->size; + + return PJ_SUCCESS; +} + +/* + * Get frame, basicy is a no-op operation. + */ +static pj_status_t file_get_frame(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + PJ_UNUSED_ARG(this_port); + PJ_UNUSED_ARG(frame); + return PJ_EINVALIDOP; +} + +/* + * Close the port, modify file header with updated file length. + */ +static pj_status_t file_on_destroy(pjmedia_port *this_port) +{ + enum { FILE_LEN_POS = 4, DATA_LEN_POS = 40 }; + struct file_port *fport = (struct file_port *)this_port; + pj_off_t file_size; + pj_ssize_t bytes; + pj_uint32_t wave_file_len; + pj_uint32_t wave_data_len; + pj_status_t status; + + /* Flush remaining buffers. */ + if (fport->writepos != fport->buf) + flush_buffer(fport); + + /* Get file size. */ + status = pj_file_getpos(fport->fd, &file_size); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Calculate wave fields */ + wave_file_len = (pj_uint32_t)(file_size - 8); + wave_data_len = (pj_uint32_t)(file_size - sizeof(pjmedia_wave_hdr)); + +#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0 + wave_file_len = pj_swap32(wave_file_len); + wave_data_len = pj_swap32(wave_data_len); +#endif + + /* Seek to the file_len field. */ + status = pj_file_setpos(fport->fd, FILE_LEN_POS, PJ_SEEK_SET); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Write file_len */ + bytes = sizeof(wave_file_len); + status = pj_file_write(fport->fd, &wave_file_len, &bytes); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Seek to data_len field. */ + status = pj_file_setpos(fport->fd, DATA_LEN_POS, PJ_SEEK_SET); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Write file_len */ + bytes = sizeof(wave_data_len); + status = pj_file_write(fport->fd, &wave_data_len, &bytes); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Close file */ + status = pj_file_close(fport->fd); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Done. */ + return PJ_SUCCESS; +} + diff --git a/pjmedia/src/pjmedia/pasound.c b/pjmedia/src/pjmedia/pasound.c index 44bac29b..769a1c14 100644 --- a/pjmedia/src/pjmedia/pasound.c +++ b/pjmedia/src/pjmedia/pasound.c @@ -131,9 +131,9 @@ static int PaPlayerCallback( const void *input, PJ_LOG(5,(THIS_FILE, "Player thread started")); } - if (statusFlags & paInputUnderflow) + if (statusFlags & paOutputUnderflow) ++stream->underflow; - if (statusFlags & paInputOverflow) + if (statusFlags & paOutputOverflow) ++stream->overflow; stream->timestamp += frameCount; @@ -239,6 +239,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, PaStreamParameters inputParam; int sampleFormat; const PaDeviceInfo *paDevInfo = NULL; + const PaHostApiInfo *paHostApiInfo = NULL; unsigned paFrames; PaError err; @@ -291,6 +292,8 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, inputParam.sampleFormat = sampleFormat; inputParam.suggestedLatency = paDevInfo->defaultLowInputLatency; + paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi); + /* Frames in PortAudio is number of samples in a single channel */ paFrames = samples_per_frame / channel_count; @@ -302,11 +305,12 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, return PJMEDIA_ERRNO_FROM_PORTAUDIO(err); } - PJ_LOG(5,(THIS_FILE, "%s opening device %s for recording, sample rate=%d, " - "channel count=%d, " - "%d bits per sample, %d samples per buffer", + PJ_LOG(5,(THIS_FILE, "%s opening device %s (%s) for recording, sample " + "rate=%d, ch=%d, " + "bits=%d, %d samples per frame", (err==0 ? "Success" : "Error"), - paDevInfo->name, clock_rate, channel_count, + paDevInfo->name, paHostApiInfo->name, + clock_rate, channel_count, bits_per_sample, samples_per_frame)); *p_snd_strm = stream; @@ -328,6 +332,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, PaStreamParameters outputParam; int sampleFormat; const PaDeviceInfo *paDevInfo = NULL; + const PaHostApiInfo *paHostApiInfo = NULL; unsigned paFrames; PaError err; @@ -380,6 +385,8 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, outputParam.sampleFormat = sampleFormat; outputParam.suggestedLatency = paDevInfo->defaultLowInputLatency; + paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi); + /* Frames in PortAudio is number of samples in a single channel */ paFrames = samples_per_frame / channel_count; @@ -391,11 +398,12 @@ PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, return PJMEDIA_ERRNO_FROM_PORTAUDIO(err); } - PJ_LOG(5,(THIS_FILE, "%s opening device %s for playing, sample rate=%d, " - "channel count=%d, " - "%d bits per sample, %d samples per frame", + PJ_LOG(5,(THIS_FILE, "%s opening device %s(%s) for playing, sample rate=%d" + ", ch=%d, " + "bits=%d, %d samples per frame", (err==0 ? "Success" : "Error"), - paDevInfo->name, clock_rate, channel_count, + paDevInfo->name, paHostApiInfo->name, + clock_rate, channel_count, bits_per_sample, samples_per_frame)); *p_snd_strm = stream; @@ -425,6 +433,8 @@ PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, int sampleFormat; const PaDeviceInfo *paRecDevInfo = NULL; const PaDeviceInfo *paPlayDevInfo = NULL; + const PaHostApiInfo *paRecHostApiInfo = NULL; + const PaHostApiInfo *paPlayHostApiInfo = NULL; unsigned paFrames; PaError err; @@ -497,12 +507,16 @@ PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, inputParam.sampleFormat = sampleFormat; inputParam.suggestedLatency = paRecDevInfo->defaultLowInputLatency; + paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi); + pj_memset(&outputParam, 0, sizeof(outputParam)); outputParam.device = play_id; outputParam.channelCount = channel_count; outputParam.hostApiSpecificStreamInfo = NULL; outputParam.sampleFormat = sampleFormat; - outputParam.suggestedLatency = paPlayDevInfo->defaultLowInputLatency; + outputParam.suggestedLatency = paPlayDevInfo->defaultLowOutputLatency; + + paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi); /* Frames in PortAudio is number of samples in a single channel */ paFrames = samples_per_frame / channel_count; @@ -515,11 +529,12 @@ PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, return PJMEDIA_ERRNO_FROM_PORTAUDIO(err); } - PJ_LOG(5,(THIS_FILE, "%s opening device %s/%s for recording and playback, " - "sample rate=%d, channel count=%d, " - "%d bits per sample, %d samples per buffer", + PJ_LOG(5,(THIS_FILE, "%s opening device %s(%s)/%s(%s) for recording and " + "playback, sample rate=%d, ch=%d, " + "bits=%d, %d samples per frame", (err==0 ? "Success" : "Error"), - paRecDevInfo->name, paPlayDevInfo->name, + paRecDevInfo->name, paRecHostApiInfo->name, + paPlayDevInfo->name, paPlayHostApiInfo->name, clock_rate, channel_count, bits_per_sample, samples_per_frame)); diff --git a/pjmedia/src/pjmedia/port.c b/pjmedia/src/pjmedia/port.c index d283b975..50a008e9 100644 --- a/pjmedia/src/pjmedia/port.c +++ b/pjmedia/src/pjmedia/port.c @@ -35,6 +35,7 @@ PJ_DEF(pj_status_t) pjmedia_port_connect( pj_pool_t *pool, PJ_ASSERT_RETURN(pool && upstream_port && downstream_port, PJ_EINVAL); +#if 0 /* They both MUST have the same media type. */ PJ_ASSERT_RETURN(upstream_port->info.type == downstream_port->info.type, PJMEDIA_ENCTYPE); @@ -57,17 +58,22 @@ PJ_DEF(pj_status_t) pjmedia_port_connect( pj_pool_t *pool, PJ_ASSERT_RETURN(upstream_port->info.bytes_per_frame == downstream_port->info.bytes_per_frame, PJMEDIA_ENCBYTES); +#endif /* Create mutual attachment. */ - status = upstream_port->on_downstream_connect( pool, upstream_port, - downstream_port ); - if (status != PJ_SUCCESS) - return status; - - status = downstream_port->on_upstream_connect( pool, downstream_port, - upstream_port ); - if (status != PJ_SUCCESS) - return status; + if (upstream_port->on_downstream_connect) { + status = upstream_port->on_downstream_connect( pool, upstream_port, + downstream_port ); + if (status != PJ_SUCCESS) + return status; + } + + if (downstream_port->on_upstream_connect) { + status = downstream_port->on_upstream_connect( pool, downstream_port, + upstream_port ); + if (status != PJ_SUCCESS) + return status; + } /* Save the attachment. */ upstream_port->downstream_port = downstream_port; diff --git a/pjmedia/src/pjmedia/resample.c b/pjmedia/src/pjmedia/resample.c index 9045a02c..95cf44ca 100644 --- a/pjmedia/src/pjmedia/resample.c +++ b/pjmedia/src/pjmedia/resample.c @@ -498,7 +498,7 @@ PJ_DEF(pj_status_t) pjmedia_resample_create( pj_pool_t *pool, * to yield the same performance. */ if (rate_out < rate_in) { - high_quality = 0; + //high_quality = 0; } #if !defined(PJMEDIA_HAS_LARGE_FILTER) || PJMEDIA_HAS_LARGE_FILTER==0 @@ -579,8 +579,7 @@ PJ_DEF(void) pjmedia_resample_run( pjmedia_resample *resample, * 0 xoff 2*xoff size+2*xoff */ dst_buf = resample->buffer + resample->xoff*2; - for (i=0; iframe_size; ++i) - dst_buf[i] = input[i]; + for (i=0; iframe_size; ++i) dst_buf[i] = input[i]; if (resample->factor >= 1) { @@ -632,3 +631,10 @@ PJ_DEF(void) pjmedia_resample_run( pjmedia_resample *resample, SrcLinear( input, output, resample->factor, resample->frame_size); } } + +PJ_DEF(unsigned) pjmedia_resample_get_input_size(pjmedia_resample *resample) +{ + PJ_ASSERT_RETURN(resample != NULL, 0); + return resample->frame_size; +} + diff --git a/pjmedia/src/pjmedia/resample_port.c b/pjmedia/src/pjmedia/resample_port.c new file mode 100644 index 00000000..d8ec7087 --- /dev/null +++ b/pjmedia/src/pjmedia/resample_port.c @@ -0,0 +1,185 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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. + * + * This program 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 +#include +#include +#include +#include + + +#define BYTES_PER_SAMPLE 2 + +struct resample_port +{ + pjmedia_port base; + pjmedia_resample *resample_get; + pjmedia_resample *resample_put; + pj_int16_t *get_buf; + pj_int16_t *put_buf; + unsigned downstream_frame_size; + unsigned upstream_frame_size; +}; + + + +static pj_status_t resample_put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame); +static pj_status_t resample_get_frame(pjmedia_port *this_port, + pjmedia_frame *frame); + + + +PJ_DEF(pj_status_t) pjmedia_resample_port_create( pj_pool_t *pool, + pj_bool_t high_quality, + pj_bool_t large_filter, + unsigned downstream_rate, + unsigned upstream_rate, + unsigned channel_count, + unsigned samples_per_frame, + pjmedia_port **p_port ) +{ + struct resample_port *rport; + unsigned upstream_samples_per_frame; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); + + upstream_samples_per_frame = (unsigned)(samples_per_frame * 1.0 * + upstream_rate / downstream_rate); + + /* Create and initialize port. */ + rport = pj_pool_zalloc(pool, sizeof(struct resample_port)); + PJ_ASSERT_RETURN(rport != NULL, PJ_ENOMEM); + + rport->base.info.bits_per_sample = 16; + rport->base.info.bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE; + rport->base.info.channel_count = channel_count; + rport->base.info.encoding_name = pj_str("pcm"); + rport->base.info.has_info = 1; + rport->base.info.name = pj_str("resample"); + rport->base.info.need_info = 0; + rport->base.info.pt = 0xFF; + rport->base.info.sample_rate = upstream_rate; + rport->base.info.samples_per_frame = upstream_samples_per_frame; + rport->base.info.signature = 0; + rport->base.info.type = PJMEDIA_TYPE_AUDIO; + + rport->downstream_frame_size = samples_per_frame; + rport->upstream_frame_size = upstream_samples_per_frame; + + /* Create buffers. + * We need separate buffer for get_frame() and put_frame() since + * both functions may run simultaneously. + */ + rport->get_buf = pj_pool_alloc(pool, samples_per_frame * BYTES_PER_SAMPLE); + PJ_ASSERT_RETURN(rport->get_buf, PJ_ENOMEM); + + rport->put_buf = pj_pool_alloc(pool, samples_per_frame * BYTES_PER_SAMPLE); + PJ_ASSERT_RETURN(rport->put_buf, PJ_ENOMEM); + + + /* Create "get_frame" resample */ + status = pjmedia_resample_create( pool, high_quality, large_filter, + downstream_rate, upstream_rate, + samples_per_frame, &rport->resample_get); + if (status != PJ_SUCCESS) + return status; + + /* Create "put_frame" resample */ + status = pjmedia_resample_create( pool, high_quality, large_filter, + upstream_rate, downstream_rate, + upstream_samples_per_frame, + &rport->resample_put); + + /* Set get_frame and put_frame interface */ + rport->base.get_frame = &resample_get_frame; + rport->base.put_frame = &resample_put_frame; + + + /* Done */ + *p_port = &rport->base; + + return PJ_SUCCESS; +} + + + +static pj_status_t resample_put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame) +{ + struct resample_port *rport = (struct resample_port*) this_port; + pjmedia_frame downstream_frame; + + /* Return if we don't have downstream port. */ + if (this_port->downstream_port == NULL) { + return PJ_SUCCESS; + } + + if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) { + pjmedia_resample_run( rport->resample_put, frame->buf, rport->put_buf); + + downstream_frame.buf = rport->put_buf; + downstream_frame.size = rport->downstream_frame_size * + BYTES_PER_SAMPLE; + } else { + downstream_frame.buf = frame->buf; + downstream_frame.size = frame->size; + } + + downstream_frame.type = frame->type; + downstream_frame.timestamp.u64 = frame->timestamp.u64; + + return pjmedia_port_put_frame( this_port->downstream_port, + &downstream_frame ); +} + + + +static pj_status_t resample_get_frame(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + struct resample_port *rport = (struct resample_port*) this_port; + pjmedia_frame downstream_frame; + pj_status_t status; + + /* Return silence if we don't have downstream port */ + if (this_port->downstream_port == NULL) { + pj_memset(frame->buf, frame->size, 0); + return PJ_SUCCESS; + } + + downstream_frame.buf = rport->get_buf; + downstream_frame.size = rport->downstream_frame_size * BYTES_PER_SAMPLE; + downstream_frame.timestamp.u64 = frame->timestamp.u64; + downstream_frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + + status = pjmedia_port_get_frame( this_port->downstream_port, + &downstream_frame); + if (status != PJ_SUCCESS) + return status; + + pjmedia_resample_run( rport->resample_get, rport->get_buf, frame->buf); + + frame->size = rport->upstream_frame_size * BYTES_PER_SAMPLE; + frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + + return PJ_SUCCESS; +} + + diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index aca74e16..b4e66845 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -70,6 +70,9 @@ static pj_status_t play_cb(/* in */ void *user_data, if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) goto no_frame; + /* Must supply the required samples */ + pj_assert(frame.size == size); + return PJ_SUCCESS; no_frame: diff --git a/pjmedia/src/pjmedia/wave.c b/pjmedia/src/pjmedia/wave.c new file mode 100644 index 00000000..29aafd5f --- /dev/null +++ b/pjmedia/src/pjmedia/wave.c @@ -0,0 +1,57 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program 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. + * + * This program 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 + +/* + * Change the endianness of WAVE header fields. + */ +static void wave_hdr_swap_bytes( pjmedia_wave_hdr *hdr ) +{ +#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0 + hdr->riff_hdr.riff = pj_swap32(hdr->riff_hdr.riff); + hdr->riff_hdr.file_len = pj_swap32(hdr->riff_hdr.file_len); + hdr->riff_hdr.wave = pj_swap32(hdr->riff_hdr.wave); + + hdr->fmt_hdr.fmt = pj_swap32(hdr->fmt_hdr.fmt); + hdr->fmt_hdr.len = pj_swap32(hdr->fmt_hdr.len); + hdr->fmt_hdr.fmt_tag = pj_swap16(hdr->fmt_hdr.fmt_tag); + hdr->fmt_hdr.nchan = pj_swap16(hdr->fmt_hdr.nchan); + hdr->fmt_hdr.sample_rate = pj_swap32(hdr->fmt_hdr.sample_rate); + hdr->fmt_hdr.bytes_per_sec = pj_swap32(hdr->fmt_hdr.bytes_per_sec); + hdr->fmt_hdr.block_align = pj_swap16(hdr->fmt_hdr.block_align); + hdr->fmt_hdr.bits_per_sample = pj_swap16(hdr->fmt_hdr.bits_per_sample); + + hdr->data_hdr.data = pj_swap32(hdr->data_hdr.data); + hdr->data_hdr.len = pj_swap32(hdr->data_hdr.len); +#else + PJ_UNUSED_ARG(hdr); +#endif +} + + +PJ_DEF(void) pjmedia_wave_hdr_file_to_host( pjmedia_wave_hdr *hdr ) +{ + wave_hdr_swap_bytes(hdr); +} + +PJ_DEF(void) pjmedia_wave_hdr_host_to_file( pjmedia_wave_hdr *hdr ) +{ + wave_hdr_swap_bytes(hdr); +} + -- cgit v1.2.3