|
| 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 | + |
0 commit comments