summaryrefslogtreecommitdiff
path: root/audio
diff options
context:
space:
mode:
Diffstat (limited to 'audio')
-rw-r--r--audio/decoder.c58
-rw-r--r--audio/decoder.h16
-rw-r--r--audio/encoder.c69
-rw-r--r--audio/encoder.h15
-rw-r--r--audio/muxer.c235
-rw-r--r--audio/muxer.h54
-rw-r--r--audio/resampler.c121
-rw-r--r--audio/resampler.h31
-rw-r--r--audio/stream.c47
-rw-r--r--audio/stream.h14
10 files changed, 660 insertions, 0 deletions
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 <libavutil/dict.h>
+
+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 <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/dict.h>
+
+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 <assert.h>
+
+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 <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+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 <asm-generic/errno-base.h>
+#include <assert.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/error.h>
+#include <libavutil/frame.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#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 <libavformat/avformat.h>
+#include <libavutil/frame.h>
+
+/**
+ * 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 <asm-generic/errno-base.h>
+#include <assert.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/error.h>
+#include <libavutil/opt.h>
+#include <libswresample/swresample.h>
+#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 <libavutil/channel_layout.h>
+#include <libavutil/samplefmt.h>
+#include <libswresample/swresample.h>
+#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 <libavformat/avformat.h>
+
+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