From c1cb78d574c0429aa5e3ff3a2b3886e4bc153212 Mon Sep 17 00:00:00 2001 From: bbergeron Date: Wed, 3 Apr 2024 17:32:01 -0400 Subject: Reset Git repo and use a pseudonym to sign commits I used to sign my commits with my real name and my personal email address, which I wanted scrubbed off the "B." pseudosphere. Re-creating a new git repository was safer than simpler than re-writing the history (although the latter could've also worked but, oh well). --- audio/decoder.c | 58 ++++++++++++++ audio/decoder.h | 16 ++++ audio/encoder.c | 69 ++++++++++++++++ audio/encoder.h | 15 ++++ audio/muxer.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ audio/muxer.h | 54 +++++++++++++ audio/resampler.c | 121 ++++++++++++++++++++++++++++ audio/resampler.h | 31 +++++++ audio/stream.c | 47 +++++++++++ audio/stream.h | 14 ++++ 10 files changed, 660 insertions(+) create mode 100644 audio/decoder.c create mode 100644 audio/decoder.h create mode 100644 audio/encoder.c create mode 100644 audio/encoder.h create mode 100644 audio/muxer.c create mode 100644 audio/muxer.h create mode 100644 audio/resampler.c create mode 100644 audio/resampler.h create mode 100644 audio/stream.c create mode 100644 audio/stream.h (limited to 'audio') diff --git a/audio/decoder.c b/audio/decoder.c new file mode 100644 index 0000000..70eacd5 --- /dev/null +++ b/audio/decoder.c @@ -0,0 +1,58 @@ +#include "decoder.h" +#include + +int decoder_init (struct Decoder *decoder, const AVCodecParameters *codecpar, AVDictionary **opts) +{ + int err; + AVCodecContext *avctx; + const AVCodec *codec; + + if ((codec = avcodec_find_decoder(codecpar->codec_id)) == NULL) + return AVERROR_DECODER_NOT_FOUND; + + /* NOTE: parameter 'codec' might be redundant here due to the next, more + * featuref-full call to avcodec_parameters_to_context. */ + if ((avctx = avcodec_alloc_context3(codec)) == NULL) + return AVERROR(ENOMEM); /* NOTE: After reading libavcodec's code, this + * error code *could* misrepresent the actual + * error, but ffmpeg won't tell us what it is + * anyway so oh well. */ + + if ((err = avcodec_parameters_to_context(avctx, codecpar)) < 0) + goto error; + + if ((err = avcodec_open2(avctx, codec, opts)) < 0) + goto error; + + /* Free previous context (no-op if avctx == NULL) and swap it with the new + * one. This allows the previous one to be left untouched in case of error, + * which is generally a nice thing to do in your code. */ + avcodec_free_context(&decoder->avctx); + decoder->avctx = avctx; + return 0; + +error: + avcodec_free_context(&avctx); + return err; +} + +int decoder_init_for_stream (struct Decoder *decoder, const AVStream *stream, AVDictionary **opts) +{ + return decoder_init(decoder, stream->codecpar, opts); +} + +int decoder_send (struct Decoder *decoder, const AVPacket *pkt) +{ + return avcodec_send_packet(decoder->avctx, pkt); +} + +int decoder_convert (struct Decoder *decoder, AVFrame *out) +{ + return avcodec_receive_frame(decoder->avctx, out); +} + +void decoder_free (struct Decoder *decoder) +{ + if (decoder == NULL) return; + avcodec_free_context(&decoder->avctx); +} diff --git a/audio/decoder.h b/audio/decoder.h new file mode 100644 index 0000000..bb7f7b1 --- /dev/null +++ b/audio/decoder.h @@ -0,0 +1,16 @@ +#ifndef AUDIO_DECODER_H_ +#define AUDIO_DECODER_H_ + +#include +#include +#include + +struct Decoder { AVCodecContext *avctx; }; + +int decoder_init (struct Decoder *decoder, const AVCodecParameters *codecpar, AVDictionary **opts); +int decoder_send (struct Decoder *decoder, const AVPacket *pkt); +int decoder_convert (struct Decoder *decoder, AVFrame *out); +void decoder_free (struct Decoder *decoder); +int decoder_init_for_stream (struct Decoder *decoder, const AVStream *stream, AVDictionary **opts); + +#endif // AUDIO_DECODER_H_ diff --git a/audio/encoder.c b/audio/encoder.c new file mode 100644 index 0000000..17cf2d2 --- /dev/null +++ b/audio/encoder.c @@ -0,0 +1,69 @@ +#include "encoder.h" +#include + +int encoder_init (struct Encoder *encoder, const AVCodecParameters *codecpar, AVDictionary **opts) +{ + /* NOTE: While encoder->time_base "MUST be set by user" when encoding + * (quoted from the official FFMPEG documentation), avcodec_open2 actually + * discard it's value and set it to '1/sample_rate'. + * + * NOTE: The assert at the end of this section serves to protect cybd in + * case the FFMPEG devs update their code to implement some funny behaviors. + */ + int err; + AVCodecContext *avctx; + const AVCodec *codec; + + if ((codec = avcodec_find_encoder(codecpar->codec_id)) == NULL) { + err = AVERROR_ENCODER_NOT_FOUND; + goto error; + } + + if ((avctx = avcodec_alloc_context3(codec)) == NULL) + return AVERROR(ENOMEM); + + if ((err = avcodec_parameters_to_context(avctx, codecpar)) < 0) + goto error; + + if ((err = avcodec_open2(avctx, codec, opts))) + goto error; + + /* Because some code rely on avctx's time base to be 1/sample_rate, and + * because this behavious currently appears undocumented, I placed some + * assert here to catch up if anything changes up with ffmpeg. */ + assert(avctx->time_base.num == 1); + assert(avctx->time_base.den == avctx->sample_rate); + encoder->avctx = avctx; + encoder->pts = 0; + return 0; + +error: + avcodec_free_context(&avctx); + return err; +} + +int encoder_init_for_stream (struct Encoder *encoder, const AVStream *stream, AVDictionary **opts) +{ + return encoder_init(encoder, stream->codecpar, opts); +} + +int encoder_send (struct Encoder *encoder, const AVFrame *frame) +{ + return avcodec_send_frame(encoder->avctx, frame); +} + +int encoder_convert (struct Encoder *encoder, AVPacket *pkt_out) +{ + int err; + err = avcodec_receive_packet(encoder->avctx, pkt_out); + if (err < 0) return err; + // Manually setting pts because FFMPEG doesn't do it consistantly + //pkt_out->pts = encoder->pts; + //encoder->pts += pkt_out->duration; + return 0; +} + +void encoder_free (struct Encoder *encoder) +{ + avcodec_free_context(&encoder->avctx); +} diff --git a/audio/encoder.h b/audio/encoder.h new file mode 100644 index 0000000..b42f862 --- /dev/null +++ b/audio/encoder.h @@ -0,0 +1,15 @@ +#ifndef AUDIO_ENCODER_H_ +#define AUDIO_ENCODER_H_ + +#include +#include + +struct Encoder { AVCodecContext *avctx; int pts; }; + +int encoder_init (struct Encoder *encoder, const AVCodecParameters *codecpar, AVDictionary **opts); +int encoder_send (struct Encoder *encoder, const AVFrame *pkt); +int encoder_convert (struct Encoder *encoder, AVPacket *out); +void encoder_free (struct Encoder *encoder); +int encoder_init_for_stream (struct Encoder *encoder, const AVStream *stream, AVDictionary **opts); + +#endif // AUDIO_ENCODER_H_ diff --git a/audio/muxer.c b/audio/muxer.c new file mode 100644 index 0000000..3860476 --- /dev/null +++ b/audio/muxer.c @@ -0,0 +1,235 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "muxer.h" +#include "encoder.h" +#include "decoder.h" +#include "resampler.h" +#include "stream.h" + +#define NANOSEC (uint64_t)1000000000 + +struct AudioMuxer { + struct Stream stream; + struct Encoder encoder; + struct Decoder decoder; + struct Resampler resampler; + AVFrame *frame_in; + AVFrame *frame_out; + AVPacket *packet; + + /* The amount of output samples that went out of this muxer. Starts at 0 and + * keep on increasing. + */ + unsigned int nb_samples_out; +}; + +int64_t audiomuxer_flush(AudioMuxer *muxer); + +uint64_t audiomuxer_get_runtime(const AudioMuxer *muxer) +{ + return (muxer->nb_samples_out * NANOSEC) / muxer->resampler.out_sample_rate; +} + +int audiomuxer_init(AudioMuxer **pmuxer, AVFormatContext *s, const struct AudioMuxerOpt *opts) +{ + int err; + AudioMuxer *muxer; + AVFrame *frame_out; + AVFrame *frame_in; + AVPacket *pkt; + + *pmuxer = NULL; + if ((muxer = malloc(sizeof(AudioMuxer))) == NULL) + return AVERROR(ENOMEM); + + muxer->decoder.avctx = NULL; + muxer->encoder.avctx = NULL; + muxer->resampler.swr = NULL; + + err = stream_init( + &muxer->stream, + s, + opts->codec_id, + opts->sample_rate, + opts->sample_format, + &opts->ch_layout, + opts->opts_muxer); + if (err) goto error; + + err = encoder_init_for_stream(&muxer->encoder, muxer->stream.av_stream, opts->opts_encoder); + if (err) goto error; + + err = resampler_init_for_encoder(&muxer->resampler, &muxer->encoder); + if (err) goto error; + + if ((frame_out = av_frame_alloc()) == NULL) + goto enomem; + if ((frame_in = av_frame_alloc()) == NULL) + goto enomem; + if ((pkt = av_packet_alloc()) == NULL) + goto enomem; + + + frame_out->format = muxer->encoder.avctx->sample_fmt; + frame_out->nb_samples = muxer->encoder.avctx->frame_size; + frame_out->ch_layout = muxer->encoder.avctx->ch_layout; + frame_out->sample_rate = muxer->encoder.avctx->sample_rate; + if ((err = av_frame_get_buffer(frame_out, 0))) + goto error; + + muxer->packet = pkt; + muxer->frame_out = frame_out; + muxer->frame_in = frame_in; + *pmuxer = muxer; + return 0; + +enomem: + err = AVERROR(ENOMEM); +error: + audiomuxer_free(muxer); + return err; +} + +int audiomuxer_conf (AudioMuxer *muxer, const AVStream *input) +{ + int err, src_rate, dst_rate; + const AVCodec *codec; + SwrContext *resampler; + + src_rate = input->codecpar->sample_rate; + + err = decoder_init_for_stream(&muxer->decoder, input, NULL); // TODO: Pass options // TODO: Pass options + if (err) { + fprintf(stderr, "Failed to configure decoder: %s\n", av_err2str(err)); + return err; + } + + err = resampler_conf_for_decoder(&muxer->resampler, &muxer->decoder); + if (err) { + fprintf(stderr, "Failed to configure resampler: %s\n", av_err2str(err)); + return err; + } + + + muxer->frame_in->format = muxer->decoder.avctx->sample_fmt; + muxer->frame_in->nb_samples = muxer->decoder.avctx->frame_size; + muxer->frame_in->ch_layout = muxer->decoder.avctx->ch_layout; + muxer->frame_in->sample_rate = muxer->decoder.avctx->sample_rate; + if ((err = av_frame_get_buffer(muxer->frame_in, 0))) + return err; + + return 0; +} + +int audiomuxer_send_packet (AudioMuxer *muxer, const AVPacket *pkt_in) +{ + int err, duration; + AVFrame *frame_in; + + frame_in = muxer->frame_in; + + if ((err = decoder_send(&muxer->decoder, pkt_in)) < 0) + return err; + + /* Since a packet might contains several frames, we call decoder_convert + * repeatedly until we're out of data. + */ + while ((err = decoder_convert(&muxer->decoder, frame_in)) != AVERROR(EAGAIN)) { + /* Errors other than EAGAIN should be repported */ + if (err) { + fprintf(stderr, "Failed to decode frame: %s\n", av_err2str(err)); + return err; + } + + /* Immediately send frame to resampler and postpone silent injection */ + if ((err = resampler_send_frame(&muxer->resampler, frame_in)) < 0) { + fprintf(stderr, "Failed to send data to resampler: %s\n", av_err2str(err)); + return err; + } + + /* Flush frame if possible */ + if ((err = audiomuxer_flush(muxer)) < 0) + return err; + } + + return 0; +} + +int audiomuxer_send_silence (AudioMuxer *muxer, unsigned int microsec) +{ + int err; + if ((err = resampler_send_empty(&muxer->resampler, microsec))) + return err; + + return audiomuxer_flush(muxer); +} + + +int64_t audiomuxer_flush(AudioMuxer *muxer) +{ + int err; + AVPacket *pkt_out; + AVFrame *frame_out; + + frame_out = muxer->frame_out; + pkt_out = muxer->packet; + + if ((err = resampler_convert(&muxer->resampler, frame_out))) { + if (err == AVERROR(EAGAIN)) return 0; + fprintf(stderr, "Failed to resample: %s\n", av_err2str(err)); + return err; + } + + if ((err = encoder_send(&muxer->encoder, frame_out))) { + fprintf(stderr, "Failed to send frame to encoder: %s\n", av_err2str(err)); + return err; + } + + if ((err = encoder_convert(&muxer->encoder, pkt_out))) { + if (err == AVERROR(EAGAIN)) return 0; + fprintf(stderr, "Failed to encode packet: %s\n", av_err2str(err)); + return err; + } + + /* pkt_out->duration is the "Duration of this packet in AVStream->time_base + * units, 0 if unknown." In practice, the timebase appears to be 1 / + * sample_rate, so, in other words, pkt_out->duration is the amount of + * samples stored in that packet. + * + * TODO: This behaviour is however undocumented, and asserts should be put + * in place to catch potential regressions. + * + * NOTE: duration is usually 1152 with a default MP3 audio stream. + */ + assert(pkt_out->duration > 0); + muxer->nb_samples_out += pkt_out->duration; + + /* Finally send packet to stream. */ + if ((err = stream_send(&muxer->stream, pkt_out)) < 0) { + fprintf(stderr, "Failed stream packet: %s\n", av_err2str(err)); + return err; + } + + return 0; +} + + +void audiomuxer_free (AudioMuxer *muxer) +{ + if (muxer == NULL) return; + + /* Flush decoder */ + avcodec_send_packet(muxer->decoder.avctx, NULL); + + encoder_free(&muxer->encoder); + decoder_free(&muxer->decoder); + resampler_free(&muxer->resampler); + + free(muxer); +} diff --git a/audio/muxer.h b/audio/muxer.h new file mode 100644 index 0000000..b671484 --- /dev/null +++ b/audio/muxer.h @@ -0,0 +1,54 @@ +#ifndef AUDIO_MUXER_H_ +#define AUDIO_MUXER_H_ + +#include +#include + +/** + * An AudioMuxer is responsible for attaching and managing an audio stream to an + * FFMPEG AVFormatContext instance. Ultimately, AudioMuxer allows users to pass + * amorphous FFMPEG audio packets to an AvFormatContext without worrying about + * encoding or sample count, and to enable silent streaming. + */ +typedef struct AudioMuxer AudioMuxer; + +struct AudioMuxerOpt { + int sample_rate; + enum AVSampleFormat sample_format; + enum AVCodecID codec_id; + AVChannelLayout ch_layout; + AVDictionary **opts_muxer; + AVDictionary **opts_encoder; +}; + +#define AUDIO_MUXER_OPT_DEFAULTS\ + .sample_rate = 44100,\ + .sample_format = 0,\ + .codec_id = 0,\ + .ch_layout = 0,\ + .opts_muxer = NULL,\ + .opts_encoder = NULL + +/** + * Initialize **pmuxer using the parameters passed by *opts and attach an audio + * stream to *avctx. This function mearly configures the output front of the + * remuxer (i.e. a stream, an encoder, and part of a resmapler), and a second + * call to audiomuxer_conf must be dispatched before sending in packets. Return + * 0 on success, or a negative AVERROR on failure. + */ +int audiomuxer_init(AudioMuxer **pmuxer, AVFormatContext *avctx, const struct AudioMuxerOpt *opts); + +/** + * Configure *muxer to process input from a given *input stream. Internally, + * this function allocate a decoder and configure the remuxer's resampler to + * suite *input's codec and sample rate. Return 0 on success, or a negative + * AVERROR on failure. + */ +int audiomuxer_conf(AudioMuxer *muxer, const AVStream *input); + +int audiomuxer_send_packet (AudioMuxer *muxer, const AVPacket *pkt_in); +int audiomuxer_send_silence (AudioMuxer *muxer, unsigned int microsec); +uint64_t audiomuxer_get_runtime (const AudioMuxer *muxer); +void audiomuxer_free (AudioMuxer *muxer); + +#endif // AUDIO_MUXER_H_ diff --git a/audio/resampler.c b/audio/resampler.c new file mode 100644 index 0000000..6906d05 --- /dev/null +++ b/audio/resampler.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include +#include "resampler.h" + +#define MICROSECOND 1000000 + +static inline uint64_t div_uint64_ceil(uint64_t num, uint64_t den) +{ + return (num / den) + (num % den != 0); +} + +static int resampler_init (struct Resampler *resampler, const AVChannelLayout *out_chlayout, enum AVSampleFormat out_sample_fmt, int64_t out_sample_rate, uint64_t out_frame_size) +{ + /* Let me tell you something; you --don't want-- CAN'T configure a + * SwrContext manually, GOOD ****ING LUCK DOING THAT. The documentation is + * so bad on that matter, it only referes to "tHe PaRaMeTeRs" or show off a + * list of deprecated attribute which looks nothing like what's actually + * used in their API. I tried to copy what's being done in their a API but + * whatever I do would either not work or be liable to change since it's not + * documented for shit (even the documented stuff doesn't appears stable, so + * wtf am I saying anyway). A ****ing joke, I tell you that. + * + * Pardon my french. + * + * Anywho, instead of configuring the input parameter directly, we shove + * them in resampler so we can recall them before calling ffmpeg's SWR + * allocator. + */ + resampler->out_chlayout = out_chlayout; + resampler->out_sample_fmt = out_sample_fmt; + resampler->out_sample_rate = out_sample_rate; + resampler->out_frame_size = out_frame_size; + resampler->swr = NULL; + return 0; +} + +static int resampler_conf (struct Resampler *resampler, const AVChannelLayout *in_chlayout, enum AVSampleFormat in_sample_fmt, int64_t in_sample_rate) +{ + int err; + + err = swr_alloc_set_opts2( + &resampler->swr, + resampler->out_chlayout, + resampler->out_sample_fmt, + resampler->out_sample_rate, + in_chlayout, + in_sample_fmt, + in_sample_rate, + 0, 0); + if (err) return err; + + if (!swr_is_initialized(resampler->swr)) + if ((err = swr_init(resampler->swr))) + return err; + + resampler->in_sample_rate = in_sample_rate; + resampler->needed = div_uint64_ceil(resampler->out_frame_size * in_sample_rate, resampler->out_sample_rate); + + return 0; +} + +int resampler_send_frame (struct Resampler *resampler, AVFrame *in) +{ + int err; + + if ((err = swr_convert(resampler->swr, NULL, 0, (const uint8_t **)in->extended_data, in->nb_samples)) < 0) + return err; + resampler->stored += in->nb_samples; + return 0; +} + +int resampler_send_empty (struct Resampler *resampler, unsigned int microsecond) +{ + // TODO: Check for downcasting + int nb_samples; + nb_samples = div_uint64_ceil(microsecond * resampler->in_sample_rate, 1000); + return swr_inject_silence(resampler->swr, nb_samples); +} + +int resampler_convert (struct Resampler *resampler, AVFrame *out) +{ + int err; + assert(out != NULL); + assert(resampler->swr != NULL); + + if (resampler->stored < resampler->needed) { + return AVERROR(EAGAIN); + /* + err = swr_inject_silence(resampler->swr, resampler->needed - resampler->stored); + if (err < 0) return err; + */ + } + + err = swr_convert(resampler->swr, out->extended_data, out->nb_samples, NULL, 0); + if (err < 0) return err; + resampler->stored = 0; + + return 0; +} + +void resampler_free (struct Resampler *resampler) +{ + if (resampler == NULL) return; + swr_free(&resampler->swr); +} + +int resampler_init_for_encoder (struct Resampler *resampler, const struct Encoder *encoder) +{ + AVCodecContext *avctx = encoder->avctx; + return resampler_init(resampler, &avctx->ch_layout, avctx->sample_fmt, avctx->sample_rate, avctx->frame_size); +} + +int resampler_conf_for_decoder (struct Resampler *resampler, const struct Decoder *decoder) +{ + AVCodecContext *avctx = decoder->avctx; + return resampler_conf(resampler, &avctx->ch_layout, avctx->sample_fmt, avctx->sample_rate); +} diff --git a/audio/resampler.h b/audio/resampler.h new file mode 100644 index 0000000..0bb471c --- /dev/null +++ b/audio/resampler.h @@ -0,0 +1,31 @@ +#ifndef AUDIO_RESAMPLER_H_ +#define AUDIO_RESAMPLER_H_ + +#include +#include +#include +#include "decoder.h" +#include "encoder.h" + +struct Resampler { + SwrContext *swr; + uint64_t stored; + uint64_t needed; + int in_sample_rate; + + /* Passed by *_init, used by *_conf */ + const AVChannelLayout *out_chlayout; + enum AVSampleFormat out_sample_fmt; + uint64_t out_sample_rate; + uint64_t out_frame_size; +}; + +int resampler_init_for_encoder (struct Resampler *resampler, const struct Encoder *encoder); +int resampler_conf_for_decoder (struct Resampler *resampler, const struct Decoder *decoder); +int resampler_send_frame (struct Resampler *resampler, AVFrame *in); +int resampler_send_empty (struct Resampler *resampler, unsigned int microsecond); +int resampler_convert (struct Resampler *resmapler, AVFrame *out); +int resampler_has_enough (struct Resampler *resampler); +void resampler_free (struct Resampler *resampler); + +#endif // AUDIO_RESAMPLER_H_ diff --git a/audio/stream.c b/audio/stream.c new file mode 100644 index 0000000..7bab674 --- /dev/null +++ b/audio/stream.c @@ -0,0 +1,47 @@ +#include "stream.h" + +int stream_init(struct Stream *stream, AVFormatContext *s, int codec_id, int sample_rate, int format, const AVChannelLayout *layout, AVDictionary **opts) +{ + int err; + AVStream *av_stream; + + if ((av_stream = avformat_new_stream(s, NULL)) == NULL) + return AVERROR(ENOMEM); + av_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + av_stream->codecpar->codec_id = codec_id; + av_stream->codecpar->sample_rate = sample_rate; + av_stream->codecpar->format = format; + av_stream->codecpar->ch_layout = *layout; + + if ((err = avformat_init_output(s, opts)) < 0) + return err; + + stream->av_stream = av_stream; + stream->s = s; + return 0; +} + +int stream_send (struct Stream *stream, AVPacket *pkt) +{ + int err; + int64_t duration; + + // NOTE: + // Before encoding; + // - frame_out->pts DOES matter + // + // After encoding; + // - pkt->pts is set to something + // - pkt->timebase is unset, but would be '1 / output_sample_rate' + // - pkt->duration is set to the amount of sample in the packet + // - pkt->dts seems to be pkt->pts + pkt->stream_index = stream->av_stream->index; + //av_packet_rescale_ts(pkt_out, encoder->time_base, stream->time_base); + //print_pts(pkt_out, &stream->time_base); + + duration = pkt->duration; + if ((err = av_interleaved_write_frame(stream->s, pkt)) < 0) + return err; + + return duration; +} diff --git a/audio/stream.h b/audio/stream.h new file mode 100644 index 0000000..94c5162 --- /dev/null +++ b/audio/stream.h @@ -0,0 +1,14 @@ +#ifndef AUDIO_STREAM_H +#define AUDIO_STREAM_H + +#include + +struct Stream { + AVFormatContext *s; + AVStream *av_stream; +}; + +int stream_init (struct Stream *stream, AVFormatContext *s, int codec_id, int sample_rate, int format, const AVChannelLayout *layout, AVDictionary **opts); +int stream_send (struct Stream *stream, AVPacket *pkt); + +#endif // AUDIO_STREAM_H -- cgit v1.2.3