Last active
March 2, 2020 13:52
-
-
Save Aniketh01/0e05df3014dc7fcdc8a4773a03086c0a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compile: gcc transcoding.c video_debugging.c -o trans -lavcodec -lavformat -lavutil -lswresample -lswscale | |
// Execute: ./trans input.mp4 out1.mp4 | |
#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 *output_extension; | |
char *muxer_opt_key; | |
char *muxer_opt_value; | |
char *video_codec; | |
char *audio_codec; | |
char *codec_priv_key; | |
char *codec_priv_value; | |
} 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_video_encoder(StreamingContext *sc, AVCodecContext *decoder_ctx, AVRational input_framerate, StreamingParams sp) | |
{ | |
sc->video_avs = avformat_new_stream(sc->avfc, NULL); | |
sc->video_avc = avcodec_find_encoder_by_name(sp.video_codec); | |
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); | |
if (sp.codec_priv_key && sp.codec_priv_value) | |
av_opt_set(sc->video_avcc->priv_data, sp.codec_priv_key, sp.codec_priv_value, 0); | |
sc->video_avcc->height = decoder_ctx->height; | |
sc->video_avcc->width = decoder_ctx->width; | |
sc->video_avcc->sample_aspect_ratio = decoder_ctx->sample_aspect_ratio; | |
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); | |
sc->audio_avc = avcodec_find_encoder_by_name(sp.audio_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->time_base = (AVRational){1, sample_rate}; | |
sc->audio_avcc->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; | |
sc->audio_avs->time_base = sc->audio_avcc->time_base; | |
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_video(StreamingContext *decoder, StreamingContext *encoder, AVFrame *input_frame) | |
{ | |
if (input_frame) | |
input_frame->pict_type = AV_PICTURE_TYPE_NONE; | |
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_video(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_video(decoder, encoder, input_frame)) | |
return -1; | |
} | |
av_frame_unref(input_frame); | |
} | |
return 0; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
if (argc < 2) | |
{ | |
logging("Inciffent count of arguments. Help: ./a.out inputfile outputfile"); | |
} | |
/* | |
* H264 -> H264 (fixed gop) | |
* Audio -> remuxed (untouched) | |
* MP4 - MP4 | |
*/ | |
StreamingParams sp = {0}; | |
sp.copy_audio = 1; | |
sp.copy_video = 0; | |
sp.video_codec = "libx264"; | |
sp.codec_priv_key = "x264-params"; | |
sp.codec_priv_value = "keyint=60:min-keyint=60:scenecut=0:slices=1:force-cfr=1"; | |
//sp.muxer_opt_key = "movflags"; | |
//sp.muxer_opt_value = "frag_keyframe+empty_moov+default_base_moof"; | |
//sp.audio_codec = "aac"; | |
//sp.output_extension = ".h264"; | |
StreamingContext *decoder = (StreamingContext *)calloc(1, sizeof(StreamingContext)); | |
decoder->filename = argv[1]; | |
StreamingContext *encoder = (StreamingContext *)calloc(1, sizeof(StreamingContext)); | |
encoder->filename = argv[2]; | |
if (sp.output_extension) | |
strcat(encoder->filename, sp.output_extension); | |
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_video_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.muxer_opt_key && sp.muxer_opt_value) | |
{ | |
av_dict_set(&muxer_opts, sp.muxer_opt_key, sp.muxer_opt_value, 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) | |
{ | |
// TODO: refactor to be generic for audio and video (receiving a function pointer to the differences) | |
if (transcode_video(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) | |
{ | |
if (transcode_audio(decoder, encoder, input_packet, input_frame)) | |
return -1; | |
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"); | |
} | |
} | |
// TODO: should I also flush the audio encoder? | |
if (encode_video(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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "video_debugging.h" | |
#include <inttypes.h> | |
#include <libavcodec/avcodec.h> | |
#include <libavformat/avformat.h> | |
#include <libavutil/opt.h> | |
#include <libavutil/timestamp.h> | |
#include <stdarg.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
void logging(const char *fmt, ...) | |
{ | |
va_list args; | |
fprintf(stderr, "LOG: "); | |
va_start(args, fmt); | |
vfprintf(stderr, fmt, args); | |
va_end(args); | |
fprintf(stderr, "\n"); | |
} | |
void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt) | |
{ | |
AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base; | |
logging("pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s " | |
"stream_index:%d", | |
av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base), | |
av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base), | |
av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base), | |
pkt->stream_index); | |
} | |
void print_timing(char *name, AVFormatContext *avf, AVCodecContext *avc, | |
AVStream *avs) | |
{ | |
logging("================================================="); | |
logging("%s", name); | |
logging("\tAVFormatContext"); | |
if (avf != NULL) | |
{ | |
logging("\t\tstart_time=%d duration=%d bit_rate=%d start_time_realtime=%d", | |
avf->start_time, avf->duration, avf->bit_rate, | |
avf->start_time_realtime); | |
} | |
else | |
{ | |
logging("\t\t->NULL"); | |
} | |
logging("\tAVCodecContext"); | |
if (avc != NULL) | |
{ | |
logging("\t\tbit_rate=%d ticks_per_frame=%d width=%d height=%d gop_size=%d " | |
"keyint_min=%d sample_rate=%d profile=%d level=%d ", | |
avc->bit_rate, avc->ticks_per_frame, avc->width, avc->height, | |
avc->gop_size, avc->keyint_min, avc->sample_rate, avc->profile, | |
avc->level); | |
logging("\t\tavc->time_base=num/den %d/%d", avc->time_base.num, | |
avc->time_base.den); | |
logging("\t\tavc->framerate=num/den %d/%d", avc->framerate.num, | |
avc->framerate.den); | |
logging("\t\tavc->pkt_timebase=num/den %d/%d", avc->pkt_timebase.num, | |
avc->pkt_timebase.den); | |
} | |
else | |
{ | |
logging("\t\t->NULL"); | |
} | |
logging("\tAVStream"); | |
if (avs != NULL) | |
{ | |
logging("\t\tindex=%d start_time=%d duration=%d ", avs->index, | |
avs->start_time, avs->duration); | |
logging("\t\tavs->time_base=num/den %d/%d", avs->time_base.num, | |
avs->time_base.den); | |
logging("\t\tavs->sample_aspect_ratio=num/den %d/%d", | |
avs->sample_aspect_ratio.num, avs->sample_aspect_ratio.den); | |
logging("\t\tavs->avg_frame_rate=num/den %d/%d", avs->avg_frame_rate.num, | |
avs->avg_frame_rate.den); | |
logging("\t\tavs->r_frame_rate=num/den %d/%d", avs->r_frame_rate.num, | |
avs->r_frame_rate.den); | |
} | |
else | |
{ | |
logging("\t\t->NULL"); | |
} | |
logging("================================================="); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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> | |
void logging(const char *fmt, ...); | |
void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt); | |
void print_timing(char *name, AVFormatContext *avf, AVCodecContext *avc, AVStream *avs); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment