Skip to content

Commit

Permalink
Merge pull request #54 from leandromoreira/transcoding-chapter
Browse files Browse the repository at this point in the history
Add transcoding example
  • Loading branch information
leandromoreira authored Jan 6, 2020
2 parents 2969dbe + 7343ccc commit cff48de
Show file tree
Hide file tree
Showing 9 changed files with 642 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
build/*
small_bunny_1080p_60fps.mp4
bunny_1080p_60fps.mp4
bunny_1s_gop.mp4
362 changes: 362 additions & 0 deletions 3_transcoding.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/timestamp.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <libavutil/opt.h>
#include <string.h>
#include <inttypes.h>
#include "video_debugging.h"

typedef struct StreamingParams {
char copy_video;
char copy_audio;
char fragmented_mp4;
char *video_codec;
char *audio_codec;
} StreamingParams;

typedef struct StreamingContext {
AVFormatContext *avfc;
AVCodec *video_avc;
AVCodec *audio_avc;
AVStream *video_avs;
AVStream *audio_avs;
AVCodecContext *video_avcc;
AVCodecContext *audio_avcc;
int video_index;
int audio_index;
char *filename;
} StreamingContext;

int fill_stream_info(AVStream *avs, AVCodec **avc, AVCodecContext **avcc) {
*avc = avcodec_find_decoder(avs->codecpar->codec_id);
if (!*avc) {logging("failed to find the codec"); return -1;}

*avcc = avcodec_alloc_context3(*avc);
if (!*avcc) {logging("failed to alloc memory for codec context"); return -1;}

if (avcodec_parameters_to_context(*avcc, avs->codecpar) < 0) {logging("failed to fill codec context"); return -1;}

if (avcodec_open2(*avcc, *avc, NULL) < 0) {logging("failed to open codec"); return -1;}
return 0;
}

int open_media(const char *in_filename, AVFormatContext **avfc) {
*avfc = avformat_alloc_context();
if (!*avfc) {logging("failed to alloc memory for format"); return -1;}

if (avformat_open_input(avfc, in_filename, NULL, NULL) != 0) {logging("failed to open input file %s", in_filename); return -1;}

if (avformat_find_stream_info(*avfc, NULL) < 0) {logging("failed to get stream info"); return -1;}
return 0;
}

int prepare_decoder(StreamingContext *sc) {
for (int i = 0; i < sc->avfc->nb_streams; i++) {
if (sc->avfc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
sc->video_avs = sc->avfc->streams[i];
sc->video_index = i;

if (fill_stream_info(sc->video_avs, &sc->video_avc, &sc->video_avcc)) {return -1;}
} else if (sc->avfc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
sc->audio_avs = sc->avfc->streams[i];
sc->audio_index = i;

if (fill_stream_info(sc->audio_avs, &sc->audio_avc, &sc->audio_avcc)) {return -1;}
} else {
logging("skipping streams other than audio and video");
}
}

return 0;
}

int prepare_encoder(StreamingContext *sc, AVCodecContext *decoder_ctx, AVRational input_framerate, StreamingParams sp) {
sc->video_avs = avformat_new_stream(sc->avfc, NULL);

char *codec_name = strcmp(sp.video_codec, "x264") == 0 ? "libx264" : "libx265";
char *x264_opts = "keyint=60:min-keyint=60:scenecut=0:force-cfr=1";
char *x265_opts = "keyint=60:min-keyint=60:scenecut=0";
char *codec_priv_key = strcmp(sp.video_codec, "x264") == 0 ? "x264-params" : "x265-params";
char *codec_priv_value = strcmp(sp.video_codec, "x264") == 0 ? x264_opts : x265_opts;

sc->video_avc = avcodec_find_encoder_by_name(codec_name);
if (!sc->video_avc) {logging("could not find the proper codec"); return -1;}

sc->video_avcc = avcodec_alloc_context3(sc->video_avc);
if (!sc->video_avcc) {logging("could not allocated memory for codec context"); return -1;}

av_opt_set(sc->video_avcc->priv_data, "preset", "fast", 0);
av_opt_set(sc->video_avcc->priv_data, codec_priv_key, codec_priv_value, 0);

sc->video_avcc->height = decoder_ctx->height;
sc->video_avcc->width = decoder_ctx->width;
if (sc->video_avc->pix_fmts)
sc->video_avcc->pix_fmt = sc->video_avc->pix_fmts[0];
else
sc->video_avcc->pix_fmt = decoder_ctx->pix_fmt;

sc->video_avcc->bit_rate = 2 * 1000 * 1000;
sc->video_avcc->rc_buffer_size = 4 * 1000 * 1000;
sc->video_avcc->rc_max_rate = 2 * 1000 * 1000;
sc->video_avcc->rc_min_rate = 2.5 * 1000 * 1000;

sc->video_avcc->time_base = av_inv_q(input_framerate);
sc->video_avs->time_base = sc->video_avcc->time_base;

if (avcodec_open2(sc->video_avcc, sc->video_avc, NULL) < 0) {logging("could not open the codec"); return -1;}
avcodec_parameters_from_context(sc->video_avs->codecpar, sc->video_avcc);
return 0;
}

int prepare_audio_encoder(StreamingContext *sc, int sample_rate, StreamingParams sp){
sc->audio_avs = avformat_new_stream(sc->avfc, NULL);

char *codec = "aac";
if (sp.audio_codec)
codec = sp.audio_codec;

sc->audio_avc = avcodec_find_encoder_by_name(codec);
if (!sc->audio_avc) {logging("could not find the proper codec"); return -1;}

sc->audio_avcc = avcodec_alloc_context3(sc->audio_avc);
if (!sc->audio_avcc) {logging("could not allocated memory for codec context"); return -1;}

int OUTPUT_CHANNELS = 2;
int OUTPUT_BIT_RATE = 196000;
sc->audio_avcc->channels = OUTPUT_CHANNELS;
sc->audio_avcc->channel_layout = av_get_default_channel_layout(OUTPUT_CHANNELS);
sc->audio_avcc->sample_rate = sample_rate;
sc->audio_avcc->sample_fmt = sc->audio_avc->sample_fmts[0];
sc->audio_avcc->bit_rate = OUTPUT_BIT_RATE;

sc->audio_avcc->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;

sc->audio_avs->time_base.den = sample_rate;
sc->audio_avs->time_base.num = 1;

if (avcodec_open2(sc->audio_avcc, sc->audio_avc, NULL) < 0) {logging("could not open the codec"); return -1;}
avcodec_parameters_from_context(sc->audio_avs->codecpar, sc->audio_avcc);
return 0;
}

int prepare_copy(AVFormatContext *avfc, AVStream **avs, AVCodecParameters *decoder_par) {
*avs = avformat_new_stream(avfc, NULL);
avcodec_parameters_copy((*avs)->codecpar, decoder_par);
return 0;
}

int remux(AVPacket **pkt, AVFormatContext **avfc, AVRational decoder_tb, AVRational encoder_tb) {
av_packet_rescale_ts(*pkt, decoder_tb, encoder_tb);
if (av_interleaved_write_frame(*avfc, *pkt) < 0) { logging("error while copying stream packet"); return -1; }
return 0;
}

int encode(StreamingContext *decoder, StreamingContext *encoder, AVFrame *input_frame) {
AVPacket *output_packet = av_packet_alloc();
if (!output_packet) {logging("could not allocate memory for output packet"); return -1;}

int response = avcodec_send_frame(encoder->video_avcc, input_frame);

while (response >= 0) {
response = avcodec_receive_packet(encoder->video_avcc, output_packet);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
} else if (response < 0) {
logging("Error while receiving packet from encoder: %s", av_err2str(response));
return -1;
}

output_packet->stream_index = decoder->video_index;
output_packet->duration = encoder->video_avs->time_base.den / encoder->video_avs->time_base.num / decoder->video_avs->avg_frame_rate.num * decoder->video_avs->avg_frame_rate.den;


av_packet_rescale_ts(output_packet, decoder->video_avs->time_base, encoder->video_avs->time_base);
response = av_interleaved_write_frame(encoder->avfc, output_packet);
if (response != 0) { logging("Error %d while receiving packet from decoder: %s", response, av_err2str(response)); return -1;}
}
av_packet_unref(output_packet);
av_packet_free(&output_packet);
return 0;
}

int encode_audio(StreamingContext *decoder, StreamingContext *encoder, AVFrame *input_frame) {
AVPacket *output_packet = av_packet_alloc();
if (!output_packet) {logging("could not allocate memory for output packet"); return -1;}

int response = avcodec_send_frame(encoder->audio_avcc, input_frame);

while (response >= 0) {
response = avcodec_receive_packet(encoder->audio_avcc, output_packet);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
} else if (response < 0) {
logging("Error while receiving packet from encoder: %s", av_err2str(response));
return -1;
}

output_packet->stream_index = decoder->audio_index;

av_packet_rescale_ts(output_packet, decoder->audio_avs->time_base, encoder->audio_avs->time_base);
response = av_interleaved_write_frame(encoder->avfc, output_packet);
if (response != 0) { logging("Error %d while receiving packet from decoder: %s", response, av_err2str(response)); return -1;}
}
av_packet_unref(output_packet);
av_packet_free(&output_packet);
return 0;
}

int transcode_audio(StreamingContext *decoder, StreamingContext *encoder, AVPacket *input_packet, AVFrame *input_frame) {
int response = avcodec_send_packet(decoder->audio_avcc, input_packet);
if (response < 0) {logging("Error while sending packet to decoder: %s", av_err2str(response)); return response;}

while (response >= 0) {
response = avcodec_receive_frame(decoder->audio_avcc, input_frame);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
} else if (response < 0) {
logging("Error while receiving frame from decoder: %s", av_err2str(response));
return response;
}

if (response >= 0) {
if (encode_audio(decoder, encoder, input_frame)) return -1;
}
av_frame_unref(input_frame);
}
return 0;
}

int transcode(StreamingContext *decoder, StreamingContext *encoder, AVPacket *input_packet, AVFrame *input_frame) {
int response = avcodec_send_packet(decoder->video_avcc, input_packet);
if (response < 0) {logging("Error while sending packet to decoder: %s", av_err2str(response)); return response;}

while (response >= 0) {
response = avcodec_receive_frame(decoder->video_avcc, input_frame);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
} else if (response < 0) {
logging("Error while receiving frame from decoder: %s", av_err2str(response));
return response;
}

if (response >= 0) {
if (encode(decoder, encoder, input_frame)) return -1;
}
av_frame_unref(input_frame);
}
return 0;
}

int main(int argc, char *argv[])
{
StreamingParams sp = {0};
sp.copy_audio = 1;
sp.copy_video = 0;
sp.fragmented_mp4 = 0;
sp.video_codec = "x264";
sp.audio_codec = "aac";

StreamingContext *decoder = (StreamingContext*) calloc(1, sizeof(StreamingContext));
decoder->filename = argv[1];

StreamingContext *encoder = (StreamingContext*) calloc(1, sizeof(StreamingContext));
encoder->filename = argv[2];

if (open_media(decoder->filename, &decoder->avfc)) return -1;
if (prepare_decoder(decoder)) return -1;

avformat_alloc_output_context2(&encoder->avfc, NULL, NULL, encoder->filename);
if (!encoder->avfc) {logging("could not allocate memory for output format");return -1;}

if (!sp.copy_video) {
AVRational input_framerate = av_guess_frame_rate(decoder->avfc, decoder->video_avs, NULL);
prepare_encoder(encoder, decoder->video_avcc, input_framerate, sp);
} else {
if (prepare_copy(encoder->avfc, &encoder->video_avs, decoder->video_avs->codecpar)) {return -1;}
}

if (!sp.copy_audio) {
if (prepare_audio_encoder(encoder, decoder->audio_avcc->sample_rate, sp)) {return -1;}
} else {
if (prepare_copy(encoder->avfc, &encoder->audio_avs, decoder->audio_avs->codecpar)) {return -1;}
}

if (encoder->avfc->oformat->flags & AVFMT_GLOBALHEADER)
encoder->avfc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

if (!(encoder->avfc->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&encoder->avfc->pb, encoder->filename, AVIO_FLAG_WRITE) < 0) {
logging("could not open the output file");
return -1;
}
}

AVDictionary* muxer_opts = NULL;

if (sp.fragmented_mp4) {
av_dict_set(&muxer_opts, "movflags", "frag_keyframe+empty_moov+default_base_moof", 0);
}

if (avformat_write_header(encoder->avfc, &muxer_opts) < 0) {logging("an error occurred when opening output file"); return -1;}

AVFrame *input_frame = av_frame_alloc();
if (!input_frame) {logging("failed to allocated memory for AVFrame"); return -1;}

AVPacket *input_packet = av_packet_alloc();
if (!input_packet) {logging("failed to allocated memory for AVPacket"); return -1;}

while (av_read_frame(decoder->avfc, input_packet) >= 0)
{
if (decoder->avfc->streams[input_packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
if (!sp.copy_video) {
if (transcode(decoder, encoder, input_packet, input_frame)) return -1;
av_packet_unref(input_packet);
} else {
if (remux(&input_packet, &encoder->avfc, decoder->video_avs->time_base, encoder->video_avs->time_base)) return -1;
}
} else if (decoder->avfc->streams[input_packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
if (!sp.copy_audio) {
transcode_audio(decoder, encoder, input_packet, input_frame);
av_packet_unref(input_packet);
} else {
if (remux(&input_packet, &encoder->avfc, decoder->audio_avs->time_base, encoder->audio_avs->time_base)) return -1;
}
} else {
logging("ignoring all non video or audio packets");
}
}
if (encode(decoder, encoder, NULL)) return -1;

av_write_trailer(encoder->avfc);

if (muxer_opts != NULL) {
av_dict_free(&muxer_opts);
muxer_opts = NULL;
}

if (input_frame != NULL) {
av_frame_free(&input_frame);
input_frame = NULL;
}

if (input_packet != NULL) {
av_packet_free(&input_packet);
input_packet = NULL;
}

avformat_close_input(&decoder->avfc);

avformat_free_context(decoder->avfc); decoder->avfc = NULL;
avformat_free_context(encoder->avfc); encoder->avfc = NULL;

avcodec_free_context(&decoder->video_avcc); decoder->video_avcc = NULL;
avcodec_free_context(&decoder->audio_avcc); decoder->audio_avcc = NULL;

free(decoder); decoder = NULL;
free(encoder); encoder = NULL;
return 0;
}

15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
usage:
echo "make fetch_small_bunny_video && make run_hello"

all: clean fetch_bbb_video make_hello run_hello make_remuxing run_remuxing_ts run_remuxing_fragmented_mp4 make_transcoding
.PHONY: all

clean:
@rm -rf ./build/*

Expand All @@ -24,3 +30,12 @@ run_remuxing_ts: make_remuxing

run_remuxing_fragmented_mp4: make_remuxing
docker run -w /files --rm -it -v `pwd`:/files leandromoreira/ffmpeg-devel /files/build/remuxing /files/small_bunny_1080p_60fps.mp4 /files/fragmented_small_bunny_1080p_60fps.mp4 fragmented

make_transcoding: clean
docker run -w /files --rm -it -v `pwd`:/files leandromoreira/ffmpeg-devel \
gcc -g -Wall -L/opt/ffmpeg/lib -I/opt/ffmpeg/include/ /files/3_transcoding.c /files/video_debugging.c \
-lavcodec -lavformat -lavfilter -lavdevice -lswresample -lswscale -lavutil \
-o /files/build/3_transcoding

run_transcoding: make_transcoding
docker run -w /files --rm -it -v `pwd`:/files leandromoreira/ffmpeg-devel ./build/3_transcoding /files/small_bunny_1080p_60fps.mp4 /files/bunny_1s_gop.mp4
Loading

0 comments on commit cff48de

Please sign in to comment.