Skip to content

Commit cff48de

Browse files
Merge pull request #54 from leandromoreira/transcoding-chapter
Add transcoding example
2 parents 2969dbe + 7343ccc commit cff48de

9 files changed

+642
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
build/*
33
small_bunny_1080p_60fps.mp4
44
bunny_1080p_60fps.mp4
5+
bunny_1s_gop.mp4

3_transcoding.c

+362
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
#include <libavcodec/avcodec.h>
2+
#include <libavformat/avformat.h>
3+
#include <libavutil/timestamp.h>
4+
#include <stdio.h>
5+
#include <stdarg.h>
6+
#include <stdlib.h>
7+
#include <libavutil/opt.h>
8+
#include <string.h>
9+
#include <inttypes.h>
10+
#include "video_debugging.h"
11+
12+
typedef struct StreamingParams {
13+
char copy_video;
14+
char copy_audio;
15+
char fragmented_mp4;
16+
char *video_codec;
17+
char *audio_codec;
18+
} StreamingParams;
19+
20+
typedef struct StreamingContext {
21+
AVFormatContext *avfc;
22+
AVCodec *video_avc;
23+
AVCodec *audio_avc;
24+
AVStream *video_avs;
25+
AVStream *audio_avs;
26+
AVCodecContext *video_avcc;
27+
AVCodecContext *audio_avcc;
28+
int video_index;
29+
int audio_index;
30+
char *filename;
31+
} StreamingContext;
32+
33+
int fill_stream_info(AVStream *avs, AVCodec **avc, AVCodecContext **avcc) {
34+
*avc = avcodec_find_decoder(avs->codecpar->codec_id);
35+
if (!*avc) {logging("failed to find the codec"); return -1;}
36+
37+
*avcc = avcodec_alloc_context3(*avc);
38+
if (!*avcc) {logging("failed to alloc memory for codec context"); return -1;}
39+
40+
if (avcodec_parameters_to_context(*avcc, avs->codecpar) < 0) {logging("failed to fill codec context"); return -1;}
41+
42+
if (avcodec_open2(*avcc, *avc, NULL) < 0) {logging("failed to open codec"); return -1;}
43+
return 0;
44+
}
45+
46+
int open_media(const char *in_filename, AVFormatContext **avfc) {
47+
*avfc = avformat_alloc_context();
48+
if (!*avfc) {logging("failed to alloc memory for format"); return -1;}
49+
50+
if (avformat_open_input(avfc, in_filename, NULL, NULL) != 0) {logging("failed to open input file %s", in_filename); return -1;}
51+
52+
if (avformat_find_stream_info(*avfc, NULL) < 0) {logging("failed to get stream info"); return -1;}
53+
return 0;
54+
}
55+
56+
int prepare_decoder(StreamingContext *sc) {
57+
for (int i = 0; i < sc->avfc->nb_streams; i++) {
58+
if (sc->avfc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
59+
sc->video_avs = sc->avfc->streams[i];
60+
sc->video_index = i;
61+
62+
if (fill_stream_info(sc->video_avs, &sc->video_avc, &sc->video_avcc)) {return -1;}
63+
} else if (sc->avfc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
64+
sc->audio_avs = sc->avfc->streams[i];
65+
sc->audio_index = i;
66+
67+
if (fill_stream_info(sc->audio_avs, &sc->audio_avc, &sc->audio_avcc)) {return -1;}
68+
} else {
69+
logging("skipping streams other than audio and video");
70+
}
71+
}
72+
73+
return 0;
74+
}
75+
76+
int prepare_encoder(StreamingContext *sc, AVCodecContext *decoder_ctx, AVRational input_framerate, StreamingParams sp) {
77+
sc->video_avs = avformat_new_stream(sc->avfc, NULL);
78+
79+
char *codec_name = strcmp(sp.video_codec, "x264") == 0 ? "libx264" : "libx265";
80+
char *x264_opts = "keyint=60:min-keyint=60:scenecut=0:force-cfr=1";
81+
char *x265_opts = "keyint=60:min-keyint=60:scenecut=0";
82+
char *codec_priv_key = strcmp(sp.video_codec, "x264") == 0 ? "x264-params" : "x265-params";
83+
char *codec_priv_value = strcmp(sp.video_codec, "x264") == 0 ? x264_opts : x265_opts;
84+
85+
sc->video_avc = avcodec_find_encoder_by_name(codec_name);
86+
if (!sc->video_avc) {logging("could not find the proper codec"); return -1;}
87+
88+
sc->video_avcc = avcodec_alloc_context3(sc->video_avc);
89+
if (!sc->video_avcc) {logging("could not allocated memory for codec context"); return -1;}
90+
91+
av_opt_set(sc->video_avcc->priv_data, "preset", "fast", 0);
92+
av_opt_set(sc->video_avcc->priv_data, codec_priv_key, codec_priv_value, 0);
93+
94+
sc->video_avcc->height = decoder_ctx->height;
95+
sc->video_avcc->width = decoder_ctx->width;
96+
if (sc->video_avc->pix_fmts)
97+
sc->video_avcc->pix_fmt = sc->video_avc->pix_fmts[0];
98+
else
99+
sc->video_avcc->pix_fmt = decoder_ctx->pix_fmt;
100+
101+
sc->video_avcc->bit_rate = 2 * 1000 * 1000;
102+
sc->video_avcc->rc_buffer_size = 4 * 1000 * 1000;
103+
sc->video_avcc->rc_max_rate = 2 * 1000 * 1000;
104+
sc->video_avcc->rc_min_rate = 2.5 * 1000 * 1000;
105+
106+
sc->video_avcc->time_base = av_inv_q(input_framerate);
107+
sc->video_avs->time_base = sc->video_avcc->time_base;
108+
109+
if (avcodec_open2(sc->video_avcc, sc->video_avc, NULL) < 0) {logging("could not open the codec"); return -1;}
110+
avcodec_parameters_from_context(sc->video_avs->codecpar, sc->video_avcc);
111+
return 0;
112+
}
113+
114+
int prepare_audio_encoder(StreamingContext *sc, int sample_rate, StreamingParams sp){
115+
sc->audio_avs = avformat_new_stream(sc->avfc, NULL);
116+
117+
char *codec = "aac";
118+
if (sp.audio_codec)
119+
codec = sp.audio_codec;
120+
121+
sc->audio_avc = avcodec_find_encoder_by_name(codec);
122+
if (!sc->audio_avc) {logging("could not find the proper codec"); return -1;}
123+
124+
sc->audio_avcc = avcodec_alloc_context3(sc->audio_avc);
125+
if (!sc->audio_avcc) {logging("could not allocated memory for codec context"); return -1;}
126+
127+
int OUTPUT_CHANNELS = 2;
128+
int OUTPUT_BIT_RATE = 196000;
129+
sc->audio_avcc->channels = OUTPUT_CHANNELS;
130+
sc->audio_avcc->channel_layout = av_get_default_channel_layout(OUTPUT_CHANNELS);
131+
sc->audio_avcc->sample_rate = sample_rate;
132+
sc->audio_avcc->sample_fmt = sc->audio_avc->sample_fmts[0];
133+
sc->audio_avcc->bit_rate = OUTPUT_BIT_RATE;
134+
135+
sc->audio_avcc->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
136+
137+
sc->audio_avs->time_base.den = sample_rate;
138+
sc->audio_avs->time_base.num = 1;
139+
140+
if (avcodec_open2(sc->audio_avcc, sc->audio_avc, NULL) < 0) {logging("could not open the codec"); return -1;}
141+
avcodec_parameters_from_context(sc->audio_avs->codecpar, sc->audio_avcc);
142+
return 0;
143+
}
144+
145+
int prepare_copy(AVFormatContext *avfc, AVStream **avs, AVCodecParameters *decoder_par) {
146+
*avs = avformat_new_stream(avfc, NULL);
147+
avcodec_parameters_copy((*avs)->codecpar, decoder_par);
148+
return 0;
149+
}
150+
151+
int remux(AVPacket **pkt, AVFormatContext **avfc, AVRational decoder_tb, AVRational encoder_tb) {
152+
av_packet_rescale_ts(*pkt, decoder_tb, encoder_tb);
153+
if (av_interleaved_write_frame(*avfc, *pkt) < 0) { logging("error while copying stream packet"); return -1; }
154+
return 0;
155+
}
156+
157+
int encode(StreamingContext *decoder, StreamingContext *encoder, AVFrame *input_frame) {
158+
AVPacket *output_packet = av_packet_alloc();
159+
if (!output_packet) {logging("could not allocate memory for output packet"); return -1;}
160+
161+
int response = avcodec_send_frame(encoder->video_avcc, input_frame);
162+
163+
while (response >= 0) {
164+
response = avcodec_receive_packet(encoder->video_avcc, output_packet);
165+
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
166+
break;
167+
} else if (response < 0) {
168+
logging("Error while receiving packet from encoder: %s", av_err2str(response));
169+
return -1;
170+
}
171+
172+
output_packet->stream_index = decoder->video_index;
173+
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;
174+
175+
176+
av_packet_rescale_ts(output_packet, decoder->video_avs->time_base, encoder->video_avs->time_base);
177+
response = av_interleaved_write_frame(encoder->avfc, output_packet);
178+
if (response != 0) { logging("Error %d while receiving packet from decoder: %s", response, av_err2str(response)); return -1;}
179+
}
180+
av_packet_unref(output_packet);
181+
av_packet_free(&output_packet);
182+
return 0;
183+
}
184+
185+
int encode_audio(StreamingContext *decoder, StreamingContext *encoder, AVFrame *input_frame) {
186+
AVPacket *output_packet = av_packet_alloc();
187+
if (!output_packet) {logging("could not allocate memory for output packet"); return -1;}
188+
189+
int response = avcodec_send_frame(encoder->audio_avcc, input_frame);
190+
191+
while (response >= 0) {
192+
response = avcodec_receive_packet(encoder->audio_avcc, output_packet);
193+
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
194+
break;
195+
} else if (response < 0) {
196+
logging("Error while receiving packet from encoder: %s", av_err2str(response));
197+
return -1;
198+
}
199+
200+
output_packet->stream_index = decoder->audio_index;
201+
202+
av_packet_rescale_ts(output_packet, decoder->audio_avs->time_base, encoder->audio_avs->time_base);
203+
response = av_interleaved_write_frame(encoder->avfc, output_packet);
204+
if (response != 0) { logging("Error %d while receiving packet from decoder: %s", response, av_err2str(response)); return -1;}
205+
}
206+
av_packet_unref(output_packet);
207+
av_packet_free(&output_packet);
208+
return 0;
209+
}
210+
211+
int transcode_audio(StreamingContext *decoder, StreamingContext *encoder, AVPacket *input_packet, AVFrame *input_frame) {
212+
int response = avcodec_send_packet(decoder->audio_avcc, input_packet);
213+
if (response < 0) {logging("Error while sending packet to decoder: %s", av_err2str(response)); return response;}
214+
215+
while (response >= 0) {
216+
response = avcodec_receive_frame(decoder->audio_avcc, input_frame);
217+
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
218+
break;
219+
} else if (response < 0) {
220+
logging("Error while receiving frame from decoder: %s", av_err2str(response));
221+
return response;
222+
}
223+
224+
if (response >= 0) {
225+
if (encode_audio(decoder, encoder, input_frame)) return -1;
226+
}
227+
av_frame_unref(input_frame);
228+
}
229+
return 0;
230+
}
231+
232+
int transcode(StreamingContext *decoder, StreamingContext *encoder, AVPacket *input_packet, AVFrame *input_frame) {
233+
int response = avcodec_send_packet(decoder->video_avcc, input_packet);
234+
if (response < 0) {logging("Error while sending packet to decoder: %s", av_err2str(response)); return response;}
235+
236+
while (response >= 0) {
237+
response = avcodec_receive_frame(decoder->video_avcc, input_frame);
238+
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
239+
break;
240+
} else if (response < 0) {
241+
logging("Error while receiving frame from decoder: %s", av_err2str(response));
242+
return response;
243+
}
244+
245+
if (response >= 0) {
246+
if (encode(decoder, encoder, input_frame)) return -1;
247+
}
248+
av_frame_unref(input_frame);
249+
}
250+
return 0;
251+
}
252+
253+
int main(int argc, char *argv[])
254+
{
255+
StreamingParams sp = {0};
256+
sp.copy_audio = 1;
257+
sp.copy_video = 0;
258+
sp.fragmented_mp4 = 0;
259+
sp.video_codec = "x264";
260+
sp.audio_codec = "aac";
261+
262+
StreamingContext *decoder = (StreamingContext*) calloc(1, sizeof(StreamingContext));
263+
decoder->filename = argv[1];
264+
265+
StreamingContext *encoder = (StreamingContext*) calloc(1, sizeof(StreamingContext));
266+
encoder->filename = argv[2];
267+
268+
if (open_media(decoder->filename, &decoder->avfc)) return -1;
269+
if (prepare_decoder(decoder)) return -1;
270+
271+
avformat_alloc_output_context2(&encoder->avfc, NULL, NULL, encoder->filename);
272+
if (!encoder->avfc) {logging("could not allocate memory for output format");return -1;}
273+
274+
if (!sp.copy_video) {
275+
AVRational input_framerate = av_guess_frame_rate(decoder->avfc, decoder->video_avs, NULL);
276+
prepare_encoder(encoder, decoder->video_avcc, input_framerate, sp);
277+
} else {
278+
if (prepare_copy(encoder->avfc, &encoder->video_avs, decoder->video_avs->codecpar)) {return -1;}
279+
}
280+
281+
if (!sp.copy_audio) {
282+
if (prepare_audio_encoder(encoder, decoder->audio_avcc->sample_rate, sp)) {return -1;}
283+
} else {
284+
if (prepare_copy(encoder->avfc, &encoder->audio_avs, decoder->audio_avs->codecpar)) {return -1;}
285+
}
286+
287+
if (encoder->avfc->oformat->flags & AVFMT_GLOBALHEADER)
288+
encoder->avfc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
289+
290+
if (!(encoder->avfc->oformat->flags & AVFMT_NOFILE)) {
291+
if (avio_open(&encoder->avfc->pb, encoder->filename, AVIO_FLAG_WRITE) < 0) {
292+
logging("could not open the output file");
293+
return -1;
294+
}
295+
}
296+
297+
AVDictionary* muxer_opts = NULL;
298+
299+
if (sp.fragmented_mp4) {
300+
av_dict_set(&muxer_opts, "movflags", "frag_keyframe+empty_moov+default_base_moof", 0);
301+
}
302+
303+
if (avformat_write_header(encoder->avfc, &muxer_opts) < 0) {logging("an error occurred when opening output file"); return -1;}
304+
305+
AVFrame *input_frame = av_frame_alloc();
306+
if (!input_frame) {logging("failed to allocated memory for AVFrame"); return -1;}
307+
308+
AVPacket *input_packet = av_packet_alloc();
309+
if (!input_packet) {logging("failed to allocated memory for AVPacket"); return -1;}
310+
311+
while (av_read_frame(decoder->avfc, input_packet) >= 0)
312+
{
313+
if (decoder->avfc->streams[input_packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
314+
if (!sp.copy_video) {
315+
if (transcode(decoder, encoder, input_packet, input_frame)) return -1;
316+
av_packet_unref(input_packet);
317+
} else {
318+
if (remux(&input_packet, &encoder->avfc, decoder->video_avs->time_base, encoder->video_avs->time_base)) return -1;
319+
}
320+
} else if (decoder->avfc->streams[input_packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
321+
if (!sp.copy_audio) {
322+
transcode_audio(decoder, encoder, input_packet, input_frame);
323+
av_packet_unref(input_packet);
324+
} else {
325+
if (remux(&input_packet, &encoder->avfc, decoder->audio_avs->time_base, encoder->audio_avs->time_base)) return -1;
326+
}
327+
} else {
328+
logging("ignoring all non video or audio packets");
329+
}
330+
}
331+
if (encode(decoder, encoder, NULL)) return -1;
332+
333+
av_write_trailer(encoder->avfc);
334+
335+
if (muxer_opts != NULL) {
336+
av_dict_free(&muxer_opts);
337+
muxer_opts = NULL;
338+
}
339+
340+
if (input_frame != NULL) {
341+
av_frame_free(&input_frame);
342+
input_frame = NULL;
343+
}
344+
345+
if (input_packet != NULL) {
346+
av_packet_free(&input_packet);
347+
input_packet = NULL;
348+
}
349+
350+
avformat_close_input(&decoder->avfc);
351+
352+
avformat_free_context(decoder->avfc); decoder->avfc = NULL;
353+
avformat_free_context(encoder->avfc); encoder->avfc = NULL;
354+
355+
avcodec_free_context(&decoder->video_avcc); decoder->video_avcc = NULL;
356+
avcodec_free_context(&decoder->audio_avcc); decoder->audio_avcc = NULL;
357+
358+
free(decoder); decoder = NULL;
359+
free(encoder); encoder = NULL;
360+
return 0;
361+
}
362+

Makefile

+15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
usage:
2+
echo "make fetch_small_bunny_video && make run_hello"
3+
4+
all: clean fetch_bbb_video make_hello run_hello make_remuxing run_remuxing_ts run_remuxing_fragmented_mp4 make_transcoding
5+
.PHONY: all
6+
17
clean:
28
@rm -rf ./build/*
39

@@ -24,3 +30,12 @@ run_remuxing_ts: make_remuxing
2430

2531
run_remuxing_fragmented_mp4: make_remuxing
2632
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
33+
34+
make_transcoding: clean
35+
docker run -w /files --rm -it -v `pwd`:/files leandromoreira/ffmpeg-devel \
36+
gcc -g -Wall -L/opt/ffmpeg/lib -I/opt/ffmpeg/include/ /files/3_transcoding.c /files/video_debugging.c \
37+
-lavcodec -lavformat -lavfilter -lavdevice -lswresample -lswscale -lavutil \
38+
-o /files/build/3_transcoding
39+
40+
run_transcoding: make_transcoding
41+
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

0 commit comments

Comments
 (0)