Added WAVE writer and resample port, and also found out why audio quality is poor with DirectSound

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@358 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
Benny Prijono 2006-03-24 20:41:20 +00:00
parent a6642d37ef
commit f808329783
19 changed files with 862 additions and 560 deletions

View File

@ -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)

View File

@ -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"

View File

@ -34,6 +34,7 @@
#include <pjmedia/jbuf.h>
#include <pjmedia/null_port.h>
#include <pjmedia/port.h>
#include <pjmedia/resample.h>
#include <pjmedia/rtcp.h>
#include <pjmedia/rtp.h>
#include <pjmedia/sdp.h>

View File

@ -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__ */

View File

@ -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 */
/************************************************************

View File

@ -26,11 +26,23 @@
#include <pjmedia/port.h>
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

View File

@ -25,10 +25,25 @@
* @file reample.h
* @brief Sample rate converter.
*/
#include "pjmedia/types.h"
#include <pjmedia/types.h>
#include <pjmedia/port.h>
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__ */

View File

@ -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__ */

View File

@ -28,15 +28,29 @@
#include <pj/pool.h>
#include <pj/string.h>
/* 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 <stdio.h>
# 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; j<conf->samples_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; i<conf->max_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; j<conf->samples_per_frame; ++j)
mix_buf[j] = 0;
for (j=0; j<conf->samples_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,

View File

@ -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" },

View File

@ -30,13 +30,10 @@
#define THIS_FILE "file_port.c"
#ifndef PJMEDIA_FILE_PORT_BUFSIZE
# define PJMEDIA_FILE_PORT_BUFSIZE 4000
#endif
#define SIGNATURE ('F'<<24|'P'<<16|'L'<<8|'Y')
#define BYTES_PER_SAMPLE 2
#define SIGNATURE ('F'<<24|'I'<<16|'L'<<8|'E')
#if 1
# define TRACE_(x) PJ_LOG(4,x)
#else
@ -44,37 +41,14 @@
#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; i<count; ++i) {
samples[i] = SWAP16(samples[i]);
samples[i] = pj_swap16(samples[i]);
}
}
#else
# define SWAP16(val16) (val16)
# define SWAP32(val32) (val32)
# define samples_to_host(samples,count)
#endif
@ -170,42 +144,12 @@ static pj_status_t fill_buffer(struct file_port *fport)
}
/* Convert samples to host rep */
samples_to_host((pj_int16_t*)fport->buf, fport->bufsize/2);
samples_to_host((pj_int16_t*)fport->buf, fport->bufsize/BYTES_PER_SAMPLE);
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.
*/
@ -223,7 +167,6 @@ PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool,
PJ_UNUSED_ARG(flags);
PJ_UNUSED_ARG(buff_size);
/* Check arguments. */
PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL);
@ -265,8 +208,10 @@ PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool,
return PJMEDIA_ENOTVALIDWAVE;
}
/* Normalize WAVE header fields value (only used in big-endian hosts) */
normalize_wave_hdr(&wave_hdr);
/* 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 ||
@ -291,7 +236,7 @@ PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool,
}
/* Block align must be 2*nchannels */
if (wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan*2) {
if (wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan*BYTES_PER_SAMPLE) {
pj_file_close(fport->fd);
return PJMEDIA_EWAVEUNSUPP;
}
@ -326,7 +271,8 @@ PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool,
/* Create file buffer.
*/
fport->bufsize = PJMEDIA_FILE_PORT_BUFSIZE;
if (buff_size < 1) buff_size = PJMEDIA_FILE_PORT_BUFSIZE;
fport->bufsize = buff_size;
/* Create buffer. */
@ -354,11 +300,12 @@ PJ_DEF(pj_status_t) pjmedia_file_player_port_create( pj_pool_t *pool,
PJ_LOG(4,(THIS_FILE,
"File port '%.*s' created: clock=%dKHz, bufsize=%uKB, "
"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/1000,
fport->base.info.sample_rate,
fport->base.info.channel_count,
fport->bufsize / 1000,
(unsigned long)(fport->fsize / 1000)));
@ -389,8 +336,9 @@ static pj_status_t file_get_frame(pjmedia_port *this_port,
pj_assert(fport->base.info.signature == SIGNATURE);
frame_size = fport->base.info.bytes_per_frame;
pj_assert(frame->size == frame_size);
//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;

View File

@ -1,448 +0,0 @@
/* $Id$ */
/*
* Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
*
* 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 <pjmedia/file_port.h>
#include <pjmedia/errno.h>
#include <pjmedia/wave.h>
#include <pj/assert.h>
#include <pj/file_access.h>
#include <pj/file_io.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/string.h>
#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; i<count; ++i) {
samples[i] = SWAP16(samples[i]);
}
}
#else
# define SWAP16(val16) (val16)
# define SWAP32(val32) (val32)
# define samples_to_host(samples,count)
#endif
struct file_port
{
pjmedia_port base;
pj_size_t bufsize;
char *buf;
char *readpos;
pj_off_t fsize;
pj_off_t fpos;
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);
static struct file_port *create_file_port(pj_pool_t *pool)
{
struct file_port *port;
port = pj_pool_zalloc(pool, sizeof(struct file_port));
if (!port)
return NULL;
port->base.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;
}

View File

@ -0,0 +1,306 @@
/* $Id$ */
/*
* Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
*
* 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 <pjmedia/file_port.h>
#include <pjmedia/errno.h>
#include <pjmedia/wave.h>
#include <pj/assert.h>
#include <pj/file_access.h>
#include <pj/file_io.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/string.h>
#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; i<count; ++i) {
samples[i] = pj_swap16(samples[i]);
}
}
#else
# define swap_samples(samples,count)
#endif
/*
* Flush the contents of the buffer to the file.
*/
static pj_status_t flush_buffer(struct file_port *fport)
{
pj_ssize_t bytes = fport->writepos - 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;
}

View File

@ -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));

View File

@ -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;
if (upstream_port->on_downstream_connect) {
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 (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;

View File

@ -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; i<resample->frame_size; ++i)
dst_buf[i] = input[i];
for (i=0; i<resample->frame_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;
}

View File

@ -0,0 +1,185 @@
/* $Id$ */
/*
* Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
*
* 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 <pjmedia/resample.h>
#include <pjmedia/errno.h>
#include <pj/assert.h>
#include <pj/pool.h>
#include <pj/string.h>
#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;
}

View File

@ -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:

View File

@ -0,0 +1,57 @@
/* $Id$ */
/*
* Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
*
* 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 <pjmedia/wave.h>
/*
* 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);
}