|
|
|
|
@ -18,6 +18,8 @@ struct decoder_s {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct output_s {
|
|
|
|
|
char *filename;
|
|
|
|
|
int clockrate;
|
|
|
|
|
AVCodecContext *avcctx;
|
|
|
|
|
AVFormatContext *fmtctx;
|
|
|
|
|
AVStream *avst;
|
|
|
|
|
@ -27,19 +29,23 @@ struct output_s {
|
|
|
|
|
|
|
|
|
|
struct decoder_def_s {
|
|
|
|
|
const char *name;
|
|
|
|
|
int clockrate_mult;
|
|
|
|
|
int avcodec_id;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define DECODER_DEF(ref, id) { \
|
|
|
|
|
#define DECODER_DEF_MULT(ref, id, mult) { \
|
|
|
|
|
.name = #ref, \
|
|
|
|
|
.avcodec_id = AV_CODEC_ID_ ## id, \
|
|
|
|
|
.clockrate_mult = mult, \
|
|
|
|
|
}
|
|
|
|
|
#define DECODER_DEF(ref, id) DECODER_DEF_MULT(ref, id, 1)
|
|
|
|
|
|
|
|
|
|
static const struct decoder_def_s decoders[] = {
|
|
|
|
|
DECODER_DEF(PCMA, PCM_ALAW),
|
|
|
|
|
DECODER_DEF(PCMU, PCM_MULAW),
|
|
|
|
|
DECODER_DEF(G723, G723_1),
|
|
|
|
|
DECODER_DEF(G722, ADPCM_G722),
|
|
|
|
|
DECODER_DEF_MULT(G722, ADPCM_G722, 2),
|
|
|
|
|
DECODER_DEF(QCELP, QCELP),
|
|
|
|
|
DECODER_DEF(G729, G729),
|
|
|
|
|
DECODER_DEF(speex, SPEEX),
|
|
|
|
|
@ -50,6 +56,11 @@ static const struct decoder_def_s decoders[] = {
|
|
|
|
|
typedef struct decoder_def_s decoder_def_t;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void output_shutdown(output_t *output);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const decoder_def_t *decoder_find(const str *name) {
|
|
|
|
|
for (int i = 0; i < G_N_ELEMENTS(decoders); i++) {
|
|
|
|
|
if (!str_cmp(name, decoders[i].name))
|
|
|
|
|
@ -75,6 +86,7 @@ decoder_t *decoder_new(const char *payload_str) {
|
|
|
|
|
ilog(LOG_WARN, "No decoder for payload %s", payload_str);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
clockrate *= def->clockrate_mult;
|
|
|
|
|
|
|
|
|
|
decoder_t *ret = g_slice_alloc0(sizeof(*ret));
|
|
|
|
|
|
|
|
|
|
@ -172,6 +184,7 @@ int decoder_input(decoder_t *dec, const str *data, unsigned long ts, output_t *o
|
|
|
|
|
|
|
|
|
|
dec->frame->pts = dec->frame->pkt_pts;
|
|
|
|
|
|
|
|
|
|
output_config(output, dec->avcctx->sample_rate);
|
|
|
|
|
output_add(output, dec->frame);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
@ -180,55 +193,69 @@ int decoder_input(decoder_t *dec, const str *data, unsigned long ts, output_t *o
|
|
|
|
|
|
|
|
|
|
output_t *output_new(const char *filename) {
|
|
|
|
|
output_t *ret = g_slice_alloc0(sizeof(*ret));
|
|
|
|
|
ret->filename = strdup(filename);
|
|
|
|
|
ret->clockrate = -1;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int output_config(output_t *output, unsigned int clockrate) {
|
|
|
|
|
// anything to do?
|
|
|
|
|
if (output->clockrate == clockrate)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
// XXX support reset/config change
|
|
|
|
|
|
|
|
|
|
output->clockrate = clockrate;
|
|
|
|
|
|
|
|
|
|
// XXX error reporting
|
|
|
|
|
ret->fmtctx = avformat_alloc_context();
|
|
|
|
|
if (!ret->fmtctx)
|
|
|
|
|
output->fmtctx = avformat_alloc_context();
|
|
|
|
|
if (!output->fmtctx)
|
|
|
|
|
goto err;
|
|
|
|
|
ret->fmtctx->oformat = av_guess_format("wav", NULL, NULL); // XXX better way?
|
|
|
|
|
if (!ret->fmtctx->oformat)
|
|
|
|
|
output->fmtctx->oformat = av_guess_format("wav", NULL, NULL); // XXX better way?
|
|
|
|
|
if (!output->fmtctx->oformat)
|
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
|
|
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE);
|
|
|
|
|
// XXX error handling
|
|
|
|
|
ret->avst = avformat_new_stream(ret->fmtctx, codec);
|
|
|
|
|
if (!ret->avst)
|
|
|
|
|
output->avst = avformat_new_stream(output->fmtctx, codec);
|
|
|
|
|
if (!output->avst)
|
|
|
|
|
goto err;
|
|
|
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0)
|
|
|
|
|
ret->avcctx = avcodec_alloc_context3(codec);
|
|
|
|
|
if (!ret->avcctx)
|
|
|
|
|
output->avcctx = avcodec_alloc_context3(codec);
|
|
|
|
|
if (!output->avcctx)
|
|
|
|
|
goto err;
|
|
|
|
|
#else
|
|
|
|
|
ret->avcctx = ret->avst->codec;
|
|
|
|
|
output->avcctx = output->avst->codec;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
ret->avcctx->channels = 1;
|
|
|
|
|
ret->avcctx->sample_rate = 8000;
|
|
|
|
|
ret->avcctx->sample_fmt = AV_SAMPLE_FMT_S16;
|
|
|
|
|
ret->avcctx->time_base = (AVRational){8000,1};
|
|
|
|
|
ret->avst->time_base = ret->avcctx->time_base;
|
|
|
|
|
output->avcctx->channels = 1;
|
|
|
|
|
output->avcctx->sample_rate = output->clockrate;
|
|
|
|
|
output->avcctx->sample_fmt = AV_SAMPLE_FMT_S16;
|
|
|
|
|
output->avcctx->time_base = (AVRational){output->clockrate,1};
|
|
|
|
|
output->avst->time_base = output->avcctx->time_base;
|
|
|
|
|
|
|
|
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0)
|
|
|
|
|
avcodec_parameters_from_context(ret->avst->codecpar, ret->avcctx);
|
|
|
|
|
avcodec_parameters_from_context(output->avst->codecpar, output->avcctx);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
int i = avcodec_open2(ret->avcctx, codec, NULL);
|
|
|
|
|
int i = avcodec_open2(output->avcctx, codec, NULL);
|
|
|
|
|
if (i)
|
|
|
|
|
goto err;
|
|
|
|
|
i = avio_open(&ret->fmtctx->pb, filename, AVIO_FLAG_WRITE);
|
|
|
|
|
i = avio_open(&output->fmtctx->pb, output->filename, AVIO_FLAG_WRITE);
|
|
|
|
|
if (i < 0)
|
|
|
|
|
goto err;
|
|
|
|
|
i = avformat_write_header(ret->fmtctx, NULL);
|
|
|
|
|
i = avformat_write_header(output->fmtctx, NULL);
|
|
|
|
|
if (i)
|
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
|
|
av_init_packet(&ret->avpkt);
|
|
|
|
|
av_init_packet(&output->avpkt);
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
output_close(ret);
|
|
|
|
|
return NULL;
|
|
|
|
|
output_shutdown(output);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -241,12 +268,24 @@ void decoder_close(decoder_t *dec) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void output_close(output_t *output) {
|
|
|
|
|
static void output_shutdown(output_t *output) {
|
|
|
|
|
if (!output)
|
|
|
|
|
return;
|
|
|
|
|
av_write_trailer(output->fmtctx);
|
|
|
|
|
avcodec_close(output->avcctx);
|
|
|
|
|
avio_closep(&output->fmtctx->pb);
|
|
|
|
|
avformat_free_context(output->fmtctx);
|
|
|
|
|
|
|
|
|
|
output->avcctx = NULL;
|
|
|
|
|
output->fmtctx = NULL;
|
|
|
|
|
output->avst = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void output_close(output_t *output) {
|
|
|
|
|
if (!output)
|
|
|
|
|
return;
|
|
|
|
|
output_shutdown(output);
|
|
|
|
|
free(output->filename);
|
|
|
|
|
g_slice_free1(sizeof(*output), output);
|
|
|
|
|
}
|
|
|
|
|
|