blob: e0f3cf5daa0711551b247ffa4d264b1811c551a4 [file] [log] [blame]
/* libSoX Opus-in-Ogg sound format handler
* Copyright (C) 2013 John Stumpo <stump@jstump.com>
*
* Largely based on vorbis.c:
* libSoX Ogg Vorbis sound format handler
* Copyright 2001, Stan Seibert <indigo@aztec.asu.edu>
*
* Portions from oggenc, (c) Michael Smith <msmith@labyrinth.net.au>,
* ogg123, (c) Kenneth Arnold <kcarnold@yahoo.com>, and
* libvorbisfile (c) Xiphophorus Company
*
* May 9, 2001 - Stan Seibert (indigo@aztec.asu.edu)
* Ogg Vorbis handler initially written.
*
* July 5, 1991 - Skeleton file
* Copyright 1991 Lance Norskog And Sundry Contributors
* This source code is freely redistributable and may be used for
* any purpose. This copyright notice must be maintained.
* Lance Norskog And Sundry Contributors are not responsible for
* the consequences of using this software.
*/
#include "third_party/sox/src/src/sox_i.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <opusfile.h>
#define DEF_BUF_LEN 4096
#define BUF_ERROR -1
#define BUF_EOF 0
#define BUF_DATA 1
typedef struct {
/* Decoding data */
OggOpusFile *of;
char *buf;
size_t buf_len;
size_t start;
size_t end; /* Unsent data samples in buf[start] through buf[end-1] */
int current_section;
int eof;
} priv_t;
/******** Callback functions used in op_open_callbacks ************/
static int callback_read(void* ft_data, unsigned char* ptr, int nbytes)
{
sox_format_t* ft = (sox_format_t*)ft_data;
return lsx_readbuf(ft, ptr, (size_t)nbytes);
}
static int callback_seek(void* ft_data, opus_int64 off, int whence)
{
sox_format_t* ft = (sox_format_t*)ft_data;
int ret = ft->seekable ? lsx_seeki(ft, (off_t)off, whence) : -1;
if (ret == EBADF)
ret = -1;
return ret;
}
static int callback_close(void* ft_data UNUSED)
{
/* Do nothing so sox can close the file for us */
return 0;
}
static opus_int64 callback_tell(void* ft_data)
{
sox_format_t* ft = (sox_format_t*)ft_data;
return lsx_tell(ft);
}
/********************* End callbacks *****************************/
/*
* Do anything required before you start reading samples.
* Read file header.
* Find out sampling rate,
* size and encoding of samples,
* mono/stereo/quad.
*/
static int startread(sox_format_t * ft)
{
priv_t * vb = (priv_t *) ft->priv;
const OpusTags *ot;
int i;
OpusFileCallbacks callbacks = {
callback_read,
callback_seek,
callback_tell,
callback_close
};
/* Init the decoder */
vb->of = op_open_callbacks(ft, &callbacks, NULL, (size_t) 0, NULL);
if (vb->of == NULL) {
lsx_fail_errno(ft, SOX_EHDR, "Input not an Ogg Opus audio stream");
return (SOX_EOF);
}
/* Get info about the Opus stream */
ot = op_tags(vb->of, -1);
/* Record audio info */
ft->signal.rate = 48000; /* libopusfile always uses 48 kHz */
ft->encoding.encoding = SOX_ENCODING_OPUS;
ft->signal.channels = op_channel_count(vb->of, -1);
/* op_pcm_total doesn't work on non-seekable files so
* skip that step in that case. Also, it reports
* "frame"-ish results so we must * channels.
*/
if (ft->seekable)
ft->signal.length = op_pcm_total(vb->of, -1) * ft->signal.channels;
/* Record comments */
for (i = 0; i < ot->comments; i++)
sox_append_comment(&ft->oob.comments, ot->user_comments[i]);
/* Setup buffer */
vb->buf_len = DEF_BUF_LEN;
vb->buf_len -= vb->buf_len % (ft->signal.channels*2); /* 2 bytes per sample */
vb->buf = lsx_calloc(vb->buf_len, sizeof(char));
vb->start = vb->end = 0;
/* Fill in other info */
vb->eof = 0;
vb->current_section = -1;
return (SOX_SUCCESS);
}
/* Refill the buffer with samples. Returns BUF_EOF if the end of the
* Opus data was reached while the buffer was being filled,
* BUF_ERROR is something bad happens, and BUF_DATA otherwise */
static int refill_buffer(sox_format_t * ft)
{
priv_t * vb = (priv_t *) ft->priv;
int num_read;
if (vb->start == vb->end) /* Samples all played */
vb->start = vb->end = 0;
while (vb->end < vb->buf_len) {
num_read = op_read(vb->of, (opus_int16*) (vb->buf + vb->end),
(int) ((vb->buf_len - vb->end) / sizeof(opus_int16)),
&vb->current_section);
if (num_read == 0)
return (BUF_EOF);
else if (num_read == OP_HOLE)
lsx_warn("Warning: hole in stream; probably harmless");
else if (num_read < 0)
return (BUF_ERROR);
else
vb->end += num_read * sizeof(opus_int16) * ft->signal.channels;
}
return (BUF_DATA);
}
/*
* Read up to len samples from file.
* Convert to signed longs.
* Place in buf[].
* Return number of samples read.
*/
static size_t read_samples(sox_format_t * ft, sox_sample_t * buf, size_t len)
{
priv_t * vb = (priv_t *) ft->priv;
size_t i;
int ret;
sox_sample_t l;
for (i = 0; i < len; i++) {
if (vb->start == vb->end) {
if (vb->eof)
break;
ret = refill_buffer(ft);
if (ret == BUF_EOF || ret == BUF_ERROR) {
vb->eof = 1;
if (vb->end == 0)
break;
}
}
l = (vb->buf[vb->start + 1] << 24)
| (0xffffff & (vb->buf[vb->start] << 16));
*(buf + i) = l;
vb->start += 2;
}
return i;
}
/*
* Do anything required when you stop reading samples.
* Don't close input file!
*/
static int stopread(sox_format_t * ft)
{
priv_t * vb = (priv_t *) ft->priv;
free(vb->buf);
op_free(vb->of);
return (SOX_SUCCESS);
}
static int seek(sox_format_t * ft, uint64_t offset)
{
priv_t * vb = (priv_t *) ft->priv;
return op_pcm_seek(vb->of, (opus_int64)(offset / ft->signal.channels))? SOX_EOF:SOX_SUCCESS;
}
LSX_FORMAT_HANDLER(opus)
{
static const char *const names[] = {"opus", NULL};
static sox_format_handler_t handler = {SOX_LIB_VERSION_CODE,
"Xiph.org's Opus lossy compression", names, 0,
startread, read_samples, stopread,
NULL, NULL, NULL,
seek, NULL, NULL, sizeof(priv_t)
};
return &handler;
}