Skip to content

Instantly share code, notes, and snippets.

@HookedBehemoth
Created October 3, 2023 23:15
Show Gist options
  • Save HookedBehemoth/9f8ae3be7f377465fce86688a8afacf2 to your computer and use it in GitHub Desktop.
Save HookedBehemoth/9f8ae3be7f377465fce86688a8afacf2 to your computer and use it in GitHub Desktop.
libavformat/libavcodec mux multiple audio files into one without reencoding
/**
* Sample code to mux multiple audio files together into one.
* This is assuming, all of them have the same channel configuration.
* Bonus:
* - Thumbnail
* - Chapters
* - Metadata
* - without any proper error handling
* - without handling incompatible thumbnail codecs
*
* Compile with
* $ gcc -lm -lssl -lcrypto ff_static.c -lavformat -lavcodec -lavutil -o ff_dynamic
*
* Minimum viable static configuration
* $ ./configure --enable-debug --disable-shared\
* --disable-autodetect --disable-iconv\
* --disable-doc --disable-htmlpages --disable-manpages --disable-podpages --disable-txtpages\
* --disable-everything\
* --disable-avdevice --disable-swresample --disable-swscale --disable-postproc --disable-avfilter --disable-dwt --disable-error-resilience --disable-lsp --disable-faan --disable-pixelutils\
* --enable-openssl\
* --enable-muxer=mov,ipod\
* --enable-demuxer=aac,mjpeg,hls,jpeg_pipe\
* --enable-parser=aac,mjpeg\
* --enable-decoder=aac,mjpeg\
* --enable-bsf=aac_adtstoasc\
* --enable-protocol=http,tcp,file,crypto,https,httpproxy
* $ make
* $ gcc -lm -lssl -lcrypto sample.c libavformat/libavformat.a libavcodec/libavcodec.a libavutil/libavutil.a -o ff_static
*/
#include <stdio.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
// example chapter count
#define CHAPTER_COUNT 3
int main()
{
const char *output_filename = "test3.m4a";
AVFormatContext *output_format_ctx = NULL, *input_format_ctx = NULL;
AVStream *input_stream, *output_stream;
int64_t last_pts = 0;
av_log_set_level(AV_LOG_TRACE);
AVChapter **chapters = av_malloc_array(CHAPTER_COUNT, sizeof(AVChapter *));
for (int i = 1; i < (CHAPTER_COUNT + 1); ++i)
{
AVChapter *chapter = chapters[i - 1] = av_mallocz(sizeof(AVChapter));
chapter->id = i;
chapter->start = last_pts;
// Sample file path. HTTP in this case
char input_audio_filename[0x100];
snprintf(input_audio_filename, 0x100, "http://127.0.0.1:8000/chapter_%02d.m3u8", i);
printf("opening %s\n", input_audio_filename);
int ret = 0;
AVDictionary *dictionary = NULL;
// NOTE: keep_alive with encryption toally broken on HLS
av_dict_set(&dictionary, "http_persistent", "false", 0);
// Open input audio file
if ((ret = avformat_open_input(&input_format_ctx, input_audio_filename, NULL, &dictionary)) != 0)
{
fprintf(stderr, "Failed to open input audio file: %s\n", av_err2str(ret));
return -1;
}
// Read input file header
if ((ret = avformat_find_stream_info(input_format_ctx, NULL)) != 0)
{
fprintf(stderr, "Error reading input file info: %s\n", av_err2str(ret));
return -1;
}
// Iterate through input audio streams and add them to the output context
for (unsigned int i = 0; i < input_format_ctx->nb_streams; i++)
{
// Ignore non-audio streams
if (input_format_ctx->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
continue;
input_stream = input_format_ctx->streams[i];
break;
}
if (input_stream == NULL)
{
fprintf(stderr, "Failed to find audio stream in input\n");
return -1;
}
if (!output_format_ctx)
{
AVFormatContext *thumbnail_format_ctx = NULL;
AVStream *thumbnail_in_stream = NULL, *thumbnail_out_stream = NULL;
// Create output format context
output_format_ctx = avformat_alloc_context();
if (!output_format_ctx)
{
fprintf(stderr, "Failed to allocate output format context\n");
return 1;
}
// Set the output format to the desired format (e.g., mp4)
avformat_alloc_output_context2(&output_format_ctx, NULL, NULL, output_filename);
output_stream = avformat_new_stream(output_format_ctx, NULL);
avcodec_parameters_copy(output_stream->codecpar, input_stream->codecpar);
// Open the output file
if (avio_open(&output_format_ctx->pb, output_filename, AVIO_FLAG_WRITE) < 0)
{
fprintf(stderr, "Failed to open output file\n");
return 1;
}
AVDictionary *metadata = NULL;
av_dict_set(&metadata, "title", "Test-1234", 0);
output_format_ctx->metadata = metadata;
// Add thumbnail
if ((ret = avformat_open_input(&thumbnail_format_ctx, "ConfusedBear.jpeg", NULL, NULL) != 0))
{
fprintf(stderr, "Opening thumbnail failed: %s\n", av_err2str(ret));
return 1;
}
// Read input file header
if ((ret = avformat_find_stream_info(thumbnail_format_ctx, NULL)) != 0)
{
fprintf(stderr, "Error reading input file info: %s\n", av_err2str(ret));
return 1;
}
// Iterate through input audio streams and add them to the output context
for (unsigned int i = 0; i < thumbnail_format_ctx->nb_streams; i++)
{
// Ignore non-video streams
if (thumbnail_format_ctx->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO)
continue;
thumbnail_in_stream = thumbnail_format_ctx->streams[i];
break;
}
if (thumbnail_in_stream == NULL)
{
fprintf(stderr, "No image stream found in thumbnail\n");
return 1;
}
thumbnail_out_stream = avformat_new_stream(output_format_ctx, NULL);
avcodec_parameters_copy(thumbnail_out_stream->codecpar, thumbnail_in_stream->codecpar);
thumbnail_out_stream->disposition = AV_DISPOSITION_ATTACHED_PIC;
// Write the output header
if ((ret = avformat_write_header(output_format_ctx, NULL)) != 0)
{
fprintf(stderr, "Failed to write output header: %s\n", av_err2str(ret));
return 1;
}
// Initialize packet for reading from the input streams
AVPacket packet = {0};
if ((ret = av_read_frame(thumbnail_format_ctx, &packet)) != 0)
{
fprintf(stderr, "failed to read thumbnail frame: %s\n", av_err2str(ret));
return 1;
}
packet.stream_index = thumbnail_out_stream->index;
if ((ret = av_write_frame(output_format_ctx, &packet)) != 0)
{
fprintf(stderr, "failed to read thumbnail frame: %s\n", av_err2str(ret));
return 1;
}
av_packet_unref(&packet);
avformat_close_input(&thumbnail_format_ctx);
}
if (output_stream == NULL)
{
fprintf(stderr, "output stream somehow NULL\n");
return -1;
}
// Initialize packet for reading from the input streams
AVPacket packet = {0};
AVRational output_time_base = output_stream->time_base; // Output timebase
int offset_pts = last_pts;
// Read and write frames from input to output
while (1)
{
// Read a packet from the input audio stream
if (av_read_frame(input_format_ctx, &packet) < 0)
{
break; // End of audio stream
}
// Regenerate PTS for the audio packet
av_packet_rescale_ts(&packet, input_stream->time_base, output_time_base);
packet.dts += offset_pts;
packet.pts += offset_pts;
last_pts += packet.duration;
// Store the modified audio frame (if needed) and write to output
if ((ret = av_write_frame(output_format_ctx, &packet)) < 0)
{
printf("av_write_frame: %s\n", av_err2str(ret));
}
av_packet_unref(&packet);
}
avformat_close_input(&input_format_ctx);
chapter->time_base = output_time_base;
chapter->end = last_pts;
av_dict_set(&chapter->metadata, "title", input_audio_filename, 0);
}
output_format_ctx->nb_chapters = CHAPTER_COUNT;
output_format_ctx->chapters = chapters;
// Write the output trailer
av_write_trailer(output_format_ctx);
// Clean up resources
avio_close(output_format_ctx->pb);
avformat_free_context(output_format_ctx);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment