diff --git a/debian/control b/debian/control index 2dc38f0fa..a6b516cda 100644 --- a/debian/control +++ b/debian/control @@ -6,6 +6,7 @@ Build-Depends: debhelper (>= 5), iptables-dev (>= 1.4), libavformat-dev, libavcodec-dev, + libavresample-dev, libavutil-dev, libcurl4-openssl-dev | libcurl4-gnutls-dev | libcurl3-openssl-dev | libcurl3-gnutls-dev, libevent-dev (>= 2.0), diff --git a/recording-daemon/Makefile b/recording-daemon/Makefile index ee9b3a7cb..6c3488421 100644 --- a/recording-daemon/Makefile +++ b/recording-daemon/Makefile @@ -9,6 +9,7 @@ CFLAGS+= `pcre-config --cflags` CFLAGS+= `pkg-config --cflags libavcodec` CFLAGS+= `pkg-config --cflags libavformat` CFLAGS+= `pkg-config --cflags libavutil` +CFLAGS+= `pkg-config --cflags libavresample` LDFLAGS= -lm LDFLAGS+= `pkg-config --libs glib-2.0` @@ -17,6 +18,7 @@ LDFLAGS+= `pcre-config --libs` LDFLAGS+= `pkg-config --libs libavcodec` LDFLAGS+= `pkg-config --libs libavformat` LDFLAGS+= `pkg-config --libs libavutil` +LDFLAGS+= `pkg-config --libs libavresample` include ../lib/lib.Makefile diff --git a/recording-daemon/decoder.c b/recording-daemon/decoder.c index 725754ab7..2a6978172 100644 --- a/recording-daemon/decoder.c +++ b/recording-daemon/decoder.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "types.h" #include "log.h" #include "str.h" @@ -12,6 +14,15 @@ struct decoder_s { + // format params + int channels; + int in_clockrate; + int out_clockrate; + + AVAudioResampleContext *avresample; + AVFrame *swr_frame; + int swr_buffers; + AVCodecContext *avcctx; AVPacket avpkt; AVFrame *frame; @@ -67,6 +78,11 @@ typedef struct decoder_def_s decoder_def_t; +int resample_audio; + + + + 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].rtpname)) @@ -77,6 +93,8 @@ static const decoder_def_t *decoder_find(const str *name) { decoder_t *decoder_new(const char *payload_str) { + const char *err = NULL; + str name; char *slash = strchr(payload_str, '/'); if (!slash) { @@ -104,24 +122,34 @@ decoder_t *decoder_new(const char *payload_str) { decoder_t *ret = g_slice_alloc0(sizeof(*ret)); - // XXX error reporting + ret->channels = channels; + ret->in_clockrate = clockrate; + ret->out_clockrate = resample_audio ? : clockrate; + AVCodec *codec = NULL; if (def->avcodec_name) codec = avcodec_find_decoder_by_name(def->avcodec_name); if (!codec) codec = avcodec_find_decoder(def->avcodec_id); + if (!codec) { + ilog(LOG_WARN, "Codec '%s' not supported", def->rtpname); + goto err; + } ret->avcctx = avcodec_alloc_context3(codec); + err = "failed to alloc codec context"; if (!ret->avcctx) goto err; ret->avcctx->channels = channels; ret->avcctx->sample_rate = clockrate; + err = "failed to open codec context"; int i = avcodec_open2(ret->avcctx, codec, NULL); if (i) goto err; av_init_packet(&ret->avpkt); ret->frame = av_frame_alloc(); + err = "failed to alloc av frame"; if (!ret->frame) goto err; @@ -132,11 +160,15 @@ decoder_t *decoder_new(const char *payload_str) { err: decoder_close(ret); + if (err) + ilog(LOG_ERR, "Error creating media decoder: %s", err); return NULL; } int decoder_input(decoder_t *dec, const str *data, unsigned long ts, output_t *output) { + const char *err; + if (G_UNLIKELY(!dec)) return -1; @@ -161,17 +193,22 @@ int decoder_input(decoder_t *dec, const str *data, unsigned long ts, output_t *o #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0) int ret = avcodec_send_packet(dec->avcctx, &dec->avpkt); dbg("send packet ret %i", ret); + err = "failed to send packet to avcodec"; if (ret) - return -1; + goto err; ret = avcodec_receive_frame(dec->avcctx, dec->frame); dbg("receive frame ret %i", ret); + err = "failed to receive frame from avcodec"; if (ret) - return -1; + goto err; #else int got_frame = 0; int ret = avcodec_decode_audio4(dec->avcctx, dec->frame, &got_frame, &dec->avpkt); dbg("decode frame ret %i, got frame %i", ret, got_frame); + err = "failed to decode audio packet"; + if (ret < 0) + goto err; if (!got_frame) return 0; #endif @@ -179,18 +216,81 @@ int decoder_input(decoder_t *dec, const str *data, unsigned long ts, output_t *o dbg("%p dec frame pts %lu pkt_pts %lu", dec, (unsigned long) dec->frame->pts, (unsigned long) dec->frame->pkt_dts); - output_config(output, dec->avcctx->sample_rate, dec->avcctx->channels); - if (output_add(output, dec->frame)) + // do we need to resample? + AVFrame *dec_frame = dec->frame; + if (dec->in_clockrate != dec->out_clockrate) { + if (!dec->avresample) { + dec->avresample = avresample_alloc_context(); + err = "failed to alloc resample context"; + if (!dec->avresample) + goto err; + + av_opt_set_int(dec->avresample, "in_channel_layout", av_get_default_channel_layout(dec->channels), 0); + av_opt_set_int(dec->avresample, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_int(dec->avresample, "in_sample_rate", dec->in_clockrate, 0); + av_opt_set_int(dec->avresample, "out_channel_layout", av_get_default_channel_layout(dec->channels), 0); + av_opt_set_int(dec->avresample, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_int(dec->avresample, "out_sample_rate", dec->out_clockrate, 0); + // av_opt_set_int(dec->avresample, "internal_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); // ? + + err = "failed to init resample context"; + if (avresample_open(dec->avresample) < 0) + goto err; + } + + // get a large enough buffer for resampled audio + int dst_samples = av_rescale_rnd(dec->frame->nb_samples, dec->out_clockrate, + dec->in_clockrate, AV_ROUND_UP); + if (!dec->swr_frame || dec->swr_buffers < dst_samples) { + av_frame_free(&dec->swr_frame); + dec->swr_frame = av_frame_alloc(); + err = "failed to alloc resampling frame"; + if (!dec->swr_frame) + goto err; + av_frame_copy_props(dec->swr_frame, dec->frame); + dec->swr_frame->format = dec->frame->format; + dec->swr_frame->channel_layout = dec->frame->channel_layout; + dec->swr_frame->nb_samples = dst_samples; + dec->swr_frame->sample_rate = dec->out_clockrate; + err = "failed to get resample buffers"; + if (av_frame_get_buffer(dec->swr_frame, 0) < 0) + goto err; + dec->swr_buffers = dst_samples; + } + + dec->swr_frame->nb_samples = dst_samples; + int ret_samples = avresample_convert(dec->avresample, dec->swr_frame->extended_data, + dec->swr_frame->linesize[0], dst_samples, + dec->frame->extended_data, + dec->frame->linesize[0], dec->frame->nb_samples); + err = "failed to resample audio"; + if (ret_samples < 0) + goto err; + + dec_frame = dec->swr_frame; + dec_frame->nb_samples = ret_samples; + dec_frame->pts = av_rescale(dec->frame->pts, dec->out_clockrate, dec->in_clockrate); + } + + output_config(output, dec->out_clockrate, dec->channels); + if (output_add(output, dec_frame)) return -1; return 0; + +err: + ilog(LOG_ERR, "Error decoding media packet: %s", err); + return -1; } void decoder_close(decoder_t *dec) { if (!dec) return; + /// XXX drain inputs and outputs avcodec_free_context(&dec->avcctx); av_frame_free(&dec->frame); + av_frame_free(&dec->swr_frame); + avresample_free(&dec->avresample); g_slice_free1(sizeof(*dec), dec); } diff --git a/recording-daemon/decoder.h b/recording-daemon/decoder.h index e2770cec5..239bab401 100644 --- a/recording-daemon/decoder.h +++ b/recording-daemon/decoder.h @@ -5,6 +5,9 @@ #include "str.h" +extern int resample_audio; + + decoder_t *decoder_new(const char *payload_str); int decoder_input(decoder_t *, const str *, unsigned long ts, output_t *); void decoder_close(decoder_t *); diff --git a/recording-daemon/main.c b/recording-daemon/main.c index 89d65aa0c..3ad7643ed 100644 --- a/recording-daemon/main.c +++ b/recording-daemon/main.c @@ -133,9 +133,11 @@ static void options(int *argc, char ***argv) { GOptionEntry e[] = { { "table", 't', 0, G_OPTION_ARG_INT, &ktable, "Kernel table rtpengine uses", "INT" }, { "spool-dir", 0, 0, G_OPTION_ARG_STRING, &spool_dir, "Directory containing rtpengine metadata files", "PATH" }, + { "num-threads", 0, 0, G_OPTION_ARG_INT, &num_threads, "Number of worker threads", "INT" }, { "output-dir", 0, 0, G_OPTION_ARG_STRING, &output_dir, "Where to write media files to", "PATH" }, { "output-format", 0, 0, G_OPTION_ARG_STRING, &output_format, "Write audio files of this type", "wav|mp3" }, - { "num-threads", 0, 0, G_OPTION_ARG_INT, &num_threads, "Number of worker threads", "INT" }, + { "resample-to", 0, 0, G_OPTION_ARG_INT, &resample_audio,"Resample all output audio", "INT" }, + { "mp3-bitrate", 0, 0, G_OPTION_ARG_INT, &mp3_bitrate, "Bits per second for MP3 encoding", "INT" }, { NULL, } }; diff --git a/recording-daemon/output.c b/recording-daemon/output.c index e037cd73f..75307f262 100644 --- a/recording-daemon/output.c +++ b/recording-daemon/output.c @@ -3,11 +3,15 @@ #include #include #include +#include +#include +#include +#include #include "log.h" struct output_s { - char *filename; + char filename[PATH_MAX]; // format params int clockrate; @@ -28,6 +32,8 @@ struct output_s { static int output_codec_id; static const char *output_file_format; +int mp3_bitrate; + static void output_shutdown(output_t *output); @@ -87,6 +93,8 @@ static int output_flush(output_t *output) { int output_add(output_t *output, AVFrame *frame) { if (!output) return -1; + if (!output->frame) // not ready - not configured + return -1; dbg("%p output fifo size %u fifo_pts %lu", output, (unsigned int) av_audio_fifo_size(output->fifo), (unsigned long) output->fifo_pts); @@ -103,16 +111,16 @@ int output_add(output_t *output, AVFrame *frame) { output_t *output_new(const char *filename) { output_t *ret = g_slice_alloc0(sizeof(*ret)); - if (asprintf(&ret->filename, "%s.%s", filename, output_file_format) <= 0) - abort(); + g_strlcpy(ret->filename, filename, sizeof(ret->filename)); ret->clockrate = -1; ret->channels = -1; - ret->frame = av_frame_alloc(); return ret; } int output_config(output_t *output, unsigned int clockrate, unsigned int channels) { + const char *err; + // anything to do? if (G_UNLIKELY(output->clockrate != clockrate)) goto format_mismatch; @@ -123,26 +131,31 @@ int output_config(output_t *output, unsigned int clockrate, unsigned int channel return 0; format_mismatch: - // XXX support reset/config change + output_shutdown(output); // copy params output->clockrate = clockrate; output->channels = channels; - // XXX error reporting + err = "failed to alloc format context"; output->fmtctx = avformat_alloc_context(); if (!output->fmtctx) goto err; output->fmtctx->oformat = av_guess_format(output_file_format, NULL, NULL); + err = "failed to determine output format"; if (!output->fmtctx->oformat) goto err; + err = "output codec not found"; AVCodec *codec = avcodec_find_encoder(output_codec_id); - // XXX error handling + if (!codec) + goto err; + err = "failed to alloc output stream"; output->avst = avformat_new_stream(output->fmtctx, codec); if (!output->avst) goto err; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0) + err = "failed to alloc codec context"; output->avcctx = avcodec_alloc_context3(codec); if (!output->avcctx) goto err; @@ -155,18 +168,35 @@ format_mismatch: output->avcctx->sample_rate = output->clockrate; output->avcctx->sample_fmt = AV_SAMPLE_FMT_S16; output->avcctx->time_base = (AVRational){output->clockrate,1}; + output->avcctx->bit_rate = mp3_bitrate; output->avst->time_base = output->avcctx->time_base; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0) avcodec_parameters_from_context(output->avst->codecpar, output->avcctx); #endif + char full_fn[PATH_MAX]; + char suff[16] = ""; + for (int i = 1; i < 20; i++) { + snprintf(full_fn, sizeof(full_fn), "%s%s.%s", output->filename, suff, output_file_format); + if (!g_file_test(full_fn, G_FILE_TEST_EXISTS)) + goto got_fn; + snprintf(suff, sizeof(suff), "-%i", i); + } + + err = "failed to find unused output file number"; + goto err; + +got_fn: + err = "failed to open output context"; int i = avcodec_open2(output->avcctx, codec, NULL); if (i) goto err; - i = avio_open(&output->fmtctx->pb, output->filename, AVIO_FLAG_WRITE); + err = "failed to open avio"; + i = avio_open(&output->fmtctx->pb, full_fn, AVIO_FLAG_WRITE); if (i < 0) goto err; + err = "failed to write header"; i = avformat_write_header(output->fmtctx, NULL); if (i) goto err; @@ -174,6 +204,7 @@ format_mismatch: av_init_packet(&output->avpkt); // output frame and fifo + output->frame = av_frame_alloc(); output->frame->nb_samples = output->avcctx->frame_size ? : 256; output->frame->format = output->avcctx->sample_fmt; output->frame->sample_rate = output->avcctx->sample_rate; @@ -190,6 +221,7 @@ format_mismatch: err: output_shutdown(output); + ilog(LOG_ERR, "Error configuring media output: %s", err); return -1; } @@ -197,6 +229,9 @@ err: static void output_shutdown(output_t *output) { if (!output) return; + if (!output->fmtctx) + return; + av_write_trailer(output->fmtctx); avcodec_close(output->avcctx); avio_closep(&output->fmtctx->pb); @@ -208,6 +243,9 @@ static void output_shutdown(output_t *output) { output->fmtctx = NULL; output->avst = NULL; output->fifo = NULL; + + output->fifo_pts = 0; + output->mux_dts = 0; } @@ -215,7 +253,6 @@ void output_close(output_t *output) { if (!output) return; output_shutdown(output); - free(output->filename); g_slice_free1(sizeof(*output), output); } diff --git a/recording-daemon/output.h b/recording-daemon/output.h index 694a387f4..184e8be7d 100644 --- a/recording-daemon/output.h +++ b/recording-daemon/output.h @@ -5,6 +5,9 @@ #include +extern int mp3_bitrate; + + void output_init(const char *format); output_t *output_new(const char *filename);