diff options
Diffstat (limited to 'pjmedia')
-rw-r--r-- | pjmedia/include/pjmedia/wav_port.h | 23 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/wave.h | 17 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/wav_player.c | 88 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/wav_writer.c | 132 |
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 */ |