summaryrefslogtreecommitdiff
path: root/pjmedia
diff options
context:
space:
mode:
authorNanang Izzuddin <nanang@teluu.com>2008-09-10 19:48:45 +0000
committerNanang Izzuddin <nanang@teluu.com>2008-09-10 19:48:45 +0000
commit5e976eec4b731070a2d04ff4eff54a0ad91926e7 (patch)
tree7d1d472a4a508f9c6546c25a3aa892b6523ef1fb /pjmedia
parent96311105ab80f66bab133b8083aad7dcdb9dee9e (diff)
Ticket #614: Added support for writing and reading WAV files encoded as 8 bit A-law/U-law.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2270 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia')
-rw-r--r--pjmedia/include/pjmedia/wav_port.h23
-rw-r--r--pjmedia/include/pjmedia/wave.h17
-rw-r--r--pjmedia/src/pjmedia/wav_player.c88
-rw-r--r--pjmedia/src/pjmedia/wav_writer.c132
4 files changed, 220 insertions, 40 deletions
diff --git a/pjmedia/include/pjmedia/wav_port.h b/pjmedia/include/pjmedia/wav_port.h
index 4385e8fc..1e133f16 100644
--- a/pjmedia/include/pjmedia/wav_port.h
+++ b/pjmedia/include/pjmedia/wav_port.h
@@ -142,6 +142,27 @@ pjmedia_wav_player_set_eof_cb( pjmedia_port *port,
*/
+/**
+ * WAV file writer options.
+ */
+enum pjmedia_file_writer_option
+{
+ /**
+ * Tell the file writer to save the audio in PCM format.
+ */
+ PJMEDIA_FILE_WRITE_PCM = 0,
+
+ /**
+ * Tell the file writer to save the audio in G711 Alaw format.
+ */
+ PJMEDIA_FILE_WRITE_ALAW = 1,
+
+ /**
+ * Tell the file writer to save the audio in G711 Alaw format.
+ */
+ PJMEDIA_FILE_WRITE_ULAW = 2,
+};
+
/**
* Create a media port to record streams to a WAV file. Note that the port
@@ -154,7 +175,7 @@ pjmedia_wav_player_set_eof_cb( pjmedia_port *port,
* @param channel_count Number of channels.
* @param samples_per_frame Number of samples per frame.
* @param bits_per_sample Number of bits per sample (eg 16).
- * @param flags Port creation flags (must be 0 at present).
+ * @param flags Port creation flags.
* @param buff_size Buffer size to be allocated. If the value is
* zero or negative, the port will use default buffer
* size (which is about 4KB).
diff --git a/pjmedia/include/pjmedia/wave.h b/pjmedia/include/pjmedia/wave.h
index 508329a5..8e268e62 100644
--- a/pjmedia/include/pjmedia/wave.h
+++ b/pjmedia/include/pjmedia/wave.h
@@ -66,11 +66,27 @@ PJ_BEGIN_DECL
*/
#define PJMEDIA_DATA_TAG ('a'<<24|'t'<<16|'a'<<8|'d')
+/**
+ * Standard FACT tag to identify fact chunks.
+ */
+#define PJMEDIA_FACT_TAG ('t'<<24|'c'<<16|'a'<<8|'f')
+
+
+/**
+ * Enumeration of format compression tag.
+ */
+typedef enum {
+ PJMEDIA_WAVE_FMT_TAG_PCM = 1,
+ PJMEDIA_WAVE_FMT_TAG_ALAW = 6,
+ PJMEDIA_WAVE_FMT_TAG_ULAW = 7
+} pjmedia_wave_fmt_tag;
+
/**
* This file describes the simpler/canonical version of a WAVE file.
* It does not support the full RIFF format specification.
*/
+#pragma pack(2)
struct pjmedia_wave_hdr
{
/** This structure describes RIFF WAVE file header */
@@ -98,6 +114,7 @@ struct pjmedia_wave_hdr
pj_uint32_t len; /**< Data length. */
} data_hdr;
};
+#pragma pack()
/**
* @see pjmedia_wave_hdr
diff --git a/pjmedia/src/pjmedia/wav_player.c b/pjmedia/src/pjmedia/wav_player.c
index b0aa40d8..ab89dab6 100644
--- a/pjmedia/src/pjmedia/wav_player.c
+++ b/pjmedia/src/pjmedia/wav_player.c
@@ -17,6 +17,7 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia/wav_port.h>
+#include <pjmedia/alaw_ulaw.h>
#include <pjmedia/errno.h>
#include <pjmedia/wave.h>
#include <pj/assert.h>
@@ -31,8 +32,7 @@
#define SIGNATURE PJMEDIA_PORT_SIGNATURE('F', 'P', 'l', 'y')
-#define BYTES_PER_SAMPLE 2
-
+#define BITS_PER_SAMPLE 16
#if 1
# define TRACE_(x) PJ_LOG(4,x)
@@ -56,6 +56,8 @@ struct file_reader_port
{
pjmedia_port base;
unsigned options;
+ pjmedia_wave_fmt_tag fmt_tag;
+ pj_uint16_t bytes_per_sample;
pj_bool_t eof;
pj_size_t bufsize;
char *buf;
@@ -145,7 +147,8 @@ static pj_status_t fill_buffer(struct file_reader_port *fport)
}
/* Convert samples to host rep */
- samples_to_host((pj_int16_t*)fport->buf, fport->bufsize/BYTES_PER_SAMPLE);
+ samples_to_host((pj_int16_t*)fport->buf,
+ fport->bufsize/fport->bytes_per_sample);
return PJ_SUCCESS;
}
@@ -165,7 +168,7 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool,
pj_ssize_t size_to_read, size_read;
struct file_reader_port *fport;
pj_off_t pos;
- pj_status_t status;
+ pj_status_t status = PJ_SUCCESS;
/* Check arguments. */
@@ -235,20 +238,34 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool,
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;
+ /* Validate format and its attributes (i.e: bits per sample, block align) */
+ switch (wave_hdr.fmt_hdr.fmt_tag) {
+ case PJMEDIA_WAVE_FMT_TAG_PCM:
+ if (wave_hdr.fmt_hdr.bits_per_sample != 16 ||
+ wave_hdr.fmt_hdr.block_align != 2 * wave_hdr.fmt_hdr.nchan)
+ status = PJMEDIA_EWAVEUNSUPP;
+ break;
+
+ case PJMEDIA_WAVE_FMT_TAG_ALAW:
+ case PJMEDIA_WAVE_FMT_TAG_ULAW:
+ if (wave_hdr.fmt_hdr.bits_per_sample != 8 ||
+ wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan)
+ status = PJMEDIA_ENOTVALIDWAVE;
+ break;
+
+ default:
+ status = PJMEDIA_EWAVEUNSUPP;
+ break;
}
- /* Block align must be 2*nchannels */
- if (wave_hdr.fmt_hdr.block_align != wave_hdr.fmt_hdr.nchan*BYTES_PER_SAMPLE) {
+ if (status != PJ_SUCCESS) {
pj_file_close(fport->fd);
- return PJMEDIA_EWAVEUNSUPP;
+ return status;
}
+ fport->fmt_tag = wave_hdr.fmt_hdr.fmt_tag;
+ fport->bytes_per_sample = wave_hdr.fmt_hdr.bits_per_sample / 8;
+
/* If length of fmt_header is greater than 16, skip the remaining
* fmt header data.
*/
@@ -299,7 +316,9 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool,
pj_file_close(fport->fd);
return PJMEDIA_EWAVEUNSUPP;
}
- if (wave_hdr.data_hdr.len < 200) {
+ if (wave_hdr.data_hdr.len < ptime * wave_hdr.fmt_hdr.sample_rate *
+ wave_hdr.fmt_hdr.nchan / 1000)
+ {
pj_file_close(fport->fd);
return PJMEDIA_EWAVETOOSHORT;
}
@@ -312,7 +331,7 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool,
/* Update port info. */
fport->base.info.channel_count = wave_hdr.fmt_hdr.nchan;
fport->base.info.clock_rate = wave_hdr.fmt_hdr.sample_rate;
- fport->base.info.bits_per_sample = wave_hdr.fmt_hdr.bits_per_sample;
+ fport->base.info.bits_per_sample = BITS_PER_SAMPLE;
fport->base.info.samples_per_frame = fport->base.info.clock_rate *
wave_hdr.fmt_hdr.nchan *
ptime / 1000;
@@ -337,7 +356,7 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool,
/* samples_per_frame must be smaller than bufsize (because get_frame()
* doesn't handle this case).
*/
- if (fport->base.info.samples_per_frame * BYTES_PER_SAMPLE >=
+ if (fport->base.info.samples_per_frame * fport->bytes_per_sample >=
fport->bufsize)
{
pj_file_close(fport->fd);
@@ -523,13 +542,21 @@ static pj_status_t file_get_frame(pjmedia_port *this_port,
fport->eof = PJ_FALSE;
}
- //frame_size = fport->base.info.bytes_per_frame;
- //pj_assert(frame->size == frame_size);
- frame_size = frame->size;
+ //pj_assert(frame->size == fport->base.info.bytes_per_frame);
+ if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_PCM) {
+ frame_size = frame->size;
+ //frame->size = frame_size;
+ } else {
+ /* Must be ULAW or ALAW */
+ pj_assert(fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ULAW ||
+ fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ALAW);
+
+ frame_size = frame->size >> 1;
+ frame->size = frame_size << 1;
+ }
/* 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))
@@ -579,6 +606,27 @@ static pj_status_t file_get_frame(pjmedia_port *this_port,
fport->readpos = fport->buf + (frame_size - endread);
}
+ if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ULAW ||
+ fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ALAW)
+ {
+ unsigned i;
+ pj_uint16_t *dst;
+ pj_uint8_t *src;
+
+ dst = (pj_uint16_t*)frame->buf + frame_size - 1;
+ src = (pj_uint8_t*)frame->buf + frame_size - 1;
+
+ if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ULAW) {
+ for (i = 0; i < frame_size; ++i) {
+ *dst-- = (pj_uint16_t) pjmedia_ulaw2linear(*src--);
+ }
+ } else {
+ for (i = 0; i < frame_size; ++i) {
+ *dst-- = (pj_uint16_t) pjmedia_alaw2linear(*src--);
+ }
+ }
+ }
+
return PJ_SUCCESS;
}
diff --git a/pjmedia/src/pjmedia/wav_writer.c b/pjmedia/src/pjmedia/wav_writer.c
index 0ceee37e..5e5696fd 100644
--- a/pjmedia/src/pjmedia/wav_writer.c
+++ b/pjmedia/src/pjmedia/wav_writer.c
@@ -17,6 +17,7 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia/wav_port.h>
+#include <pjmedia/alaw_ulaw.h>
#include <pjmedia/errno.h>
#include <pjmedia/wave.h>
#include <pj/assert.h>
@@ -29,12 +30,14 @@
#define THIS_FILE "wav_writer.c"
#define SIGNATURE PJMEDIA_PORT_SIGNATURE('F', 'W', 'R', 'T')
-#define BYTES_PER_SAMPLE 2
struct file_port
{
pjmedia_port base;
+ pjmedia_wave_fmt_tag fmt_tag;
+ pj_uint16_t bytes_per_sample;
+
pj_size_t bufsize;
char *buf;
char *writepos;
@@ -72,8 +75,6 @@ PJ_DEF(pj_status_t) pjmedia_wav_writer_port_create( pj_pool_t *pool,
pj_str_t name;
pj_status_t status;
- PJ_UNUSED_ARG(flags);
-
/* Check arguments. */
PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL);
@@ -96,6 +97,16 @@ PJ_DEF(pj_status_t) pjmedia_wav_writer_port_create( pj_pool_t *pool,
fport->base.put_frame = &file_put_frame;
fport->base.on_destroy = &file_on_destroy;
+ if (flags == PJMEDIA_FILE_WRITE_ALAW) {
+ fport->fmt_tag = PJMEDIA_WAVE_FMT_TAG_ALAW;
+ fport->bytes_per_sample = 1;
+ } else if (flags == PJMEDIA_FILE_WRITE_ULAW) {
+ fport->fmt_tag = PJMEDIA_WAVE_FMT_TAG_ULAW;
+ fport->bytes_per_sample = 1;
+ } else {
+ fport->fmt_tag = PJMEDIA_WAVE_FMT_TAG_PCM;
+ fport->bytes_per_sample = 2;
+ }
/* Open file in write and read mode.
* We need the read mode because we'll modify the WAVE header once
@@ -113,14 +124,13 @@ PJ_DEF(pj_status_t) pjmedia_wav_writer_port_create( pj_pool_t *pool,
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.fmt_tag = fport->fmt_tag;
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;
+ fport->bytes_per_sample;
+ wave_hdr.fmt_hdr.block_align = fport->bytes_per_sample * channel_count;
+ wave_hdr.fmt_hdr.bits_per_sample = fport->bytes_per_sample * 8;
wave_hdr.data_hdr.data = PJMEDIA_DATA_TAG;
wave_hdr.data_hdr.len = 0; /* will be filled later */
@@ -133,11 +143,51 @@ PJ_DEF(pj_status_t) pjmedia_wav_writer_port_create( pj_pool_t *pool,
/* 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;
+ if (fport->fmt_tag != PJMEDIA_WAVE_FMT_TAG_PCM) {
+ pjmedia_wave_subchunk fact_chunk;
+ pj_uint32_t tmp = 0;
+
+ fact_chunk.id = PJMEDIA_FACT_TAG;
+ fact_chunk.len = 4;
+
+ PJMEDIA_WAVE_NORMALIZE_SUBCHUNK(&fact_chunk);
+
+ /* Write WAVE header without DATA chunk header */
+ size = sizeof(pjmedia_wave_hdr) - sizeof(wave_hdr.data_hdr);
+ status = pj_file_write(fport->fd, &wave_hdr, &size);
+ if (status != PJ_SUCCESS) {
+ pj_file_close(fport->fd);
+ return status;
+ }
+
+ /* Write FACT chunk if it stores compressed data */
+ size = sizeof(fact_chunk);
+ status = pj_file_write(fport->fd, &fact_chunk, &size);
+ if (status != PJ_SUCCESS) {
+ pj_file_close(fport->fd);
+ return status;
+ }
+ size = 4;
+ status = pj_file_write(fport->fd, &tmp, &size);
+ if (status != PJ_SUCCESS) {
+ pj_file_close(fport->fd);
+ return status;
+ }
+
+ /* Write DATA chunk header */
+ size = sizeof(wave_hdr.data_hdr);
+ status = pj_file_write(fport->fd, &wave_hdr.data_hdr, &size);
+ if (status != PJ_SUCCESS) {
+ pj_file_close(fport->fd);
+ return status;
+ }
+ } else {
+ 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. */
@@ -258,9 +308,15 @@ static pj_status_t file_put_frame(pjmedia_port *this_port,
const pjmedia_frame *frame)
{
struct file_port *fport = (struct file_port *)this_port;
+ unsigned frame_size;
+
+ if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_PCM)
+ frame_size = frame->size;
+ else
+ frame_size = frame->size >> 1;
/* Flush buffer if we don't have enough room for the frame. */
- if (fport->writepos + frame->size > fport->buf + fport->bufsize) {
+ if (fport->writepos + frame_size > fport->buf + fport->bufsize) {
pj_status_t status;
status = flush_buffer(fport);
if (status != PJ_SUCCESS)
@@ -268,15 +324,32 @@ static pj_status_t file_put_frame(pjmedia_port *this_port,
}
/* Check if frame is not too large. */
- PJ_ASSERT_RETURN(fport->writepos+frame->size <= fport->buf+fport->bufsize,
+ 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;
+ if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_PCM) {
+ pj_memcpy(fport->writepos, frame->buf, frame->size);
+ } else {
+ unsigned i;
+ pj_int16_t *src = (pj_int16_t*)frame->buf;
+ pj_uint8_t *dst = (pj_uint8_t*)fport->writepos;
+
+ if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ULAW) {
+ for (i = 0; i < frame_size; ++i) {
+ *dst++ = pjmedia_linear2ulaw(*src++);
+ }
+ } else {
+ for (i = 0; i < frame_size; ++i) {
+ *dst++ = pjmedia_linear2alaw(*src++);
+ }
+ }
+
+ }
+ fport->writepos += frame_size;
/* Increment total written, and check if we need to call callback */
- fport->total += frame->size;
+ fport->total += frame_size;
if (fport->cb && fport->total >= fport->cb_size) {
pj_status_t (*cb)(pjmedia_port*, void*);
pj_status_t status;
@@ -314,6 +387,7 @@ static pj_status_t file_on_destroy(pjmedia_port *this_port)
pj_uint32_t wave_file_len;
pj_uint32_t wave_data_len;
pj_status_t status;
+ pj_uint32_t data_len_pos = DATA_LEN_POS;
/* Flush remaining buffers. */
if (fport->writepos != fport->buf)
@@ -341,8 +415,28 @@ static pj_status_t file_on_destroy(pjmedia_port *this_port)
status = pj_file_write(fport->fd, &wave_file_len, &bytes);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ /* Write samples_len in FACT chunk */
+ if (fport->fmt_tag != PJMEDIA_WAVE_FMT_TAG_PCM) {
+ enum { SAMPLES_LEN_POS = 44};
+ pj_uint32_t wav_samples_len;
+
+ /* Adjust wave_data_len & data_len_pos since there is FACT chunk */
+ wave_data_len -= 12;
+ data_len_pos += 12;
+ wav_samples_len = wave_data_len;
+
+ /* Seek to samples_len field. */
+ status = pj_file_setpos(fport->fd, SAMPLES_LEN_POS, PJ_SEEK_SET);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Write samples_len */
+ bytes = sizeof(wav_samples_len);
+ status = pj_file_write(fport->fd, &wav_samples_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);
+ status = pj_file_setpos(fport->fd, data_len_pos, PJ_SEEK_SET);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Write file_len */