From 38c6c782aa9530c61537e74b1e1781d59f3eddc8 Mon Sep 17 00:00:00 2001 From: Leandro Moreira Date: Thu, 2 Jan 2020 11:36:07 -0300 Subject: [PATCH] add basic transcoding --- 3_transcoding.c | 145 ++++++++++++++++++++++++++++++++++++++---------- Makefile | 2 +- 2 files changed, 117 insertions(+), 30 deletions(-) diff --git a/3_transcoding.c b/3_transcoding.c index 36dece9..10671dc 100644 --- a/3_transcoding.c +++ b/3_transcoding.c @@ -75,14 +75,14 @@ int prepare_decoder(StreamingContext *sc) { sc->video_index = i; if (fill_stream_info(sc->video_avs, &sc->video_avc, &sc->video_avcc)) {return -1;} - print_timing("Video timming info from the decoder preparation", sc->avfc, sc->video_avcc, sc->video_avs); + //print_timing("Video timming info from the decoder preparation", sc->avfc, sc->video_avcc, sc->video_avs); } else if (sc->avfc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // keeping a pointer to the audio av stream 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;} - print_timing("Audio timming info from the decoder preparation", sc->avfc, sc->audio_avcc, sc->audio_avs); + //print_timing("Audio timming info from the decoder preparation", sc->avfc, sc->audio_avcc, sc->audio_avs); } else { logging("skipping streams other than audio and video"); } @@ -91,10 +91,9 @@ int prepare_decoder(StreamingContext *sc) { return 0; } -int prepare_encoder(StreamingContext *sc, AVCodecParameters *decoder_par, AVRational input_framerate) { - /* - * Prepping the encoder - video stream to be transcoded - */ +int prepare_encoder(StreamingContext *sc, AVCodecContext *decoder_ctx, AVRational input_framerate) { + sc->video_avs = avformat_new_stream(sc->avfc, NULL); + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__encoding.html#gaa614ffc38511c104bdff4a3afa086d37 // Find a registered encoder with the specified name. sc->video_avc = avcodec_find_encoder_by_name("libx264"); @@ -105,32 +104,41 @@ int prepare_encoder(StreamingContext *sc, AVCodecParameters *decoder_par, AVRati sc->video_avcc = avcodec_alloc_context3(sc->video_avc); if (!sc->video_avcc) {logging("could not allocated memory for codec context"); return -1;} - //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#gac7b282f51540ca7a99416a3ba6ee0d16 - // Fill the codec context based on the values from the supplied codec parameters. - if(avcodec_parameters_to_context(sc->video_avcc, decoder_par) < 0){logging("failed to copy codec parameters from the decoder"); return -1;} - print_timing("Video encoder 1", sc->avfc, sc->video_avcc, sc->video_avs); - av_opt_set(sc->video_avcc->priv_data, "preset", "slow", 0); //av_opt_set(sc->video_avcc->priv_data, "x264opts", "keyint=60:min-keyint=60:scenecut=-1", 0); - AVDictionary *encoder_private_options = NULL; - //av_dict_set(&encoder_private_options , "b", "2.0M", 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; + + // set up pixel format + 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->time_base = input_framerate; + //sc->video_avcc->time_base = (AVRational){1,1}; + + //print_timing("Video encoder 1", sc->avfc, sc->video_avcc, sc->video_avs); + sc->video_avs->time_base = input_framerate; + + + AVDictionary *encoder_private_options = NULL; + // TODO: understand the difference between this and ->priv_data + //av_dict_set(&encoder_private_options , "b", "2.0M", 0); + // is this related to http://www.chaneru.com/Roku/HLS/X264_Settings.htm ? //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#ga11f785a188d7d9df71621001465b0f1d // Initialize the AVCodecContext to use the given AVCodec. (does it resets the avcc?) if (avcodec_open2(sc->video_avcc, sc->video_avc, &encoder_private_options) < 0) {logging("could not open the codec"); return -1;} - print_timing("Video encoder 2", sc->avfc, sc->video_avcc, sc->video_avs); - - sc->video_avs = avformat_new_stream(sc->avfc, NULL); - //sc->video_avs->time_base = (AVRational){1, input_framerate}; + //print_timing("Video encoder 2", sc->avfc, sc->video_avcc, sc->video_avs); - //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#ga6d02e640ccc12c783841ce51d09b9fa7 - // Any allocated fields in dst are freed and replaced with newly allocated duplicates of the corresponding fields in src. - avcodec_parameters_copy(sc->video_avs->codecpar, decoder_par); - print_timing("Video timming info from the encoder preparation", sc->avfc, sc->video_avcc, sc->video_avs); - print_timing("Video encoder 3", sc->avfc, sc->video_avcc, sc->video_avs); + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#ga0c7058f764778615e7978a1821ab3cfe + // Fill the parameters struct based on the values from the supplied codec context. + avcodec_parameters_from_context(sc->video_avs->codecpar, sc->video_avcc); + // TODO: I want to base my setting on the decoder but I'm copying from my encoder + //print_timing("Video encoder 3", sc->avfc, sc->video_avcc, sc->video_avs); return 0; } @@ -151,17 +159,90 @@ int remux(AVPacket **pkt, AVFormatContext **avfc, AVRational decoder_tb, AVRatio //https://www.ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga37352ed2c63493c38219d935e71db6c1 // Write a packet to an output media file ensuring correct interleaving if (av_interleaved_write_frame(*avfc, *pkt) < 0) { logging("error while copying stream packet"); return -1; } - av_packet_unref(*pkt); 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;} + + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__decoding.html#ga9395cb802a5febf1f00df31497779169 + // Supply a raw video or audio frame to the encoder. + int response = avcodec_send_frame(encoder->video_avcc, input_frame); + + while (response >= 0) { + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__decoding.html#ga5b8eff59cf259747cf0b31563e38ded6 + // Read encoded data from the encoder. + 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; + } + + /* prepare packet for muxing */ + //output_packet->stream_index = input_packet->stream_index; // ??????????????????? decoder->video_index + output_packet->stream_index = decoder->video_index; // ??????????????????? decoder->video_index + // you need to set the package duration otherwise + // it might present fps/dts/pts issues (thanks James and Vassilis from mailing list) + 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; + + //output_packet->stream_index = input_packet->stream_index; + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__packet.html#gae5c86e4d93f6e7aa62ef2c60763ea67e + // Convert valid timing fields (timestamps / durations) in a packet from one timebase to another. + av_packet_rescale_ts(output_packet, decoder->video_avs->time_base, encoder->video_avs->time_base); + //output_packet->stream_index = input_packet->stream_index; ????????????? + + //https://www.ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga37352ed2c63493c38219d935e71db6c1 + // Write a packet to an output media file ensuring correct interleaving. + 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(StreamingContext *decoder, StreamingContext *encoder, AVPacket *input_packet, AVFrame *input_frame) { + /* + * Decoding video stream so we can re-encode it + */ + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__decoding.html#ga58bc4bf1e0ac59e27362597e467efff3 + // Supply raw packet data as input to a decoder. + 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) { + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__decoding.html#ga11e6542c4e66d3028668788a1a74217c + // Return decoded output data from a decoder. + 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; + } + + /* + * Re-encoding the frame since it was decoded successfully + */ + if (response >= 0) { + if (encode(decoder, encoder, input_frame)) return -1; + } + av_frame_unref(input_frame); + } + return 0; +} int main(int argc, char *argv[]) { + //av_log_set_level(AV_LOG_DEBUG); StreamingParams sp = {0}; sp.copy_audio = 1; - sp.copy_video = 1; - sp.fragmented_mp4 = 1; + sp.copy_video = 0; + sp.fragmented_mp4 = 0; StreamingContext *decoder = (StreamingContext*) calloc(1, sizeof(StreamingContext)); decoder->filename = argv[1]; @@ -194,7 +275,8 @@ int main(int argc, char *argv[]) // for video if (!sp.copy_video) { // transcoding - // TODO: + AVRational input_framerate = av_guess_frame_rate(decoder->avfc, decoder->video_avs, NULL); + prepare_encoder(encoder, decoder->video_avcc, input_framerate); } else { // just copying if (prepare_copy(encoder->avfc, &encoder->video_avs, decoder->video_avs->codecpar)) {return -1;} @@ -247,8 +329,6 @@ int main(int argc, char *argv[]) AVPacket *input_packet = av_packet_alloc(); if (!input_packet) {logging("failed to allocated memory for AVPacket"); return -1;} - int response = 0; - //https://www.ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#ga4fdb3084415a82e3810de6ee60e46a61 // Return the next frame of a stream. while (av_read_frame(decoder->avfc, input_packet) >= 0) @@ -256,7 +336,8 @@ int main(int argc, char *argv[]) if (decoder->avfc->streams[input_packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (!sp.copy_video) { // transcoding - // TODO + if (transcode(decoder, encoder, input_packet, input_frame)) return -1; + av_packet_unref(input_packet); // TODO: should I add this for all? } else { // just copying if (remux(&input_packet, &encoder->avfc, decoder->video_avs->time_base, encoder->video_avs->time_base)) return -1; @@ -268,11 +349,17 @@ int main(int argc, char *argv[]) } else { // just copying if (remux(&input_packet, &encoder->avfc, decoder->audio_avs->time_base, encoder->audio_avs->time_base)) return -1; + //av_packet_free(&input_packet); } } else { logging("ignoring all non video or audio packets"); } } + // to flush ????????? + if (encode(decoder, encoder, NULL)) return -1; + + + //https://www.ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga7f14007e7dc8f481f054b21614dfec13 // Write the stream trailer to an output media file and free the file private data. av_write_trailer(encoder->avfc); diff --git a/Makefile b/Makefile index 46d6485..0cdb0b3 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ run_remuxing_fragmented_mp4: make_remuxing make_transcoding: clean docker run -w /files --rm -it -v `pwd`:/files leandromoreira/ffmpeg-devel \ - gcc -W -L/opt/ffmpeg/lib -I/opt/ffmpeg/include/ /files/3_transcoding.c /files/video_debugging.c \ + 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