|
| 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 | +} StreamingParams; |
| 16 | + |
| 17 | +typedef struct StreamingContext { |
| 18 | + AVFormatContext *avfc; |
| 19 | + AVCodec *video_avc; |
| 20 | + AVCodec *audio_avc; |
| 21 | + AVStream *video_avs; |
| 22 | + AVStream *audio_avs; |
| 23 | + AVCodecContext *video_avcc; |
| 24 | + AVCodecContext *audio_avcc; |
| 25 | + int video_index; |
| 26 | + int audio_index; |
| 27 | + char *filename; |
| 28 | +} StreamingContext; |
| 29 | + |
| 30 | +int fill_stream_info(AVStream *avs, AVCodec **avc, AVCodecContext **avcc) { |
| 31 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__decoding.html#ga19a0ca553277f019dd5b0fec6e1f9dca |
| 32 | + // Find a registered decoder with a matching codec ID. |
| 33 | + *avc = avcodec_find_decoder(avs->codecpar->codec_id); |
| 34 | + if (!*avc) {logging("failed to find the codec"); return -1;} |
| 35 | + |
| 36 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#gae80afec6f26df6607eaacf39b561c315 |
| 37 | + // Allocate an AVCodecContext and set its fields to default values. |
| 38 | + *avcc = avcodec_alloc_context3(*avc); |
| 39 | + if (!*avcc) {logging("failed to alloc memory for codec context"); return -1;} |
| 40 | + |
| 41 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#gac7b282f51540ca7a99416a3ba6ee0d16 |
| 42 | + // Fill the codec context based on the values from the supplied codec parameters. |
| 43 | + if (avcodec_parameters_to_context(*avcc, avs->codecpar) < 0) {logging("failed to fill codec context"); return -1;} |
| 44 | + |
| 45 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#ga11f785a188d7d9df71621001465b0f1d |
| 46 | + // Initialize the AVCodecContext to use the given AVCodec. |
| 47 | + if (avcodec_open2(*avcc, *avc, NULL) < 0) {logging("failed to open codec"); return -1;} |
| 48 | + return 0; |
| 49 | +} |
| 50 | + |
| 51 | +int open_media(const char *in_filename, AVFormatContext **avfc) { |
| 52 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavf__core.html#gac7a91abf2f59648d995894711f070f62 |
| 53 | + // Allocate an AVFormatContext. |
| 54 | + *avfc = avformat_alloc_context(); |
| 55 | + if (!*avfc) {logging("failed to alloc memory for format"); return -1;} |
| 56 | + |
| 57 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#gaff468dcc45289542f4c30d311bc2a201 |
| 58 | + // Open an input stream and read the header. |
| 59 | + if (avformat_open_input(avfc, in_filename, NULL, NULL) != 0) {logging("failed to open input file %s", in_filename); return -1;} |
| 60 | + |
| 61 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#gad42172e27cddafb81096939783b157bb |
| 62 | + // Read packets of a media file to get stream information. |
| 63 | + if (avformat_find_stream_info(*avfc, NULL) < 0) {logging("failed to get stream info"); return -1;} |
| 64 | + return 0; |
| 65 | +} |
| 66 | + |
| 67 | +int prepare_decoder(StreamingContext *sc) { |
| 68 | + // iterating through all the input streams (audio, video, subtitles and etc) |
| 69 | + // if we have multiples streams of audio and video we're going to take the last |
| 70 | + for (int i = 0; i < sc->avfc->nb_streams; i++) { |
| 71 | + if (sc->avfc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { |
| 72 | + // keeping a pointer to the video av stream |
| 73 | + sc->video_avs = sc->avfc->streams[i]; |
| 74 | + sc->video_index = i; |
| 75 | + |
| 76 | + if (fill_stream_info(sc->video_avs, &sc->video_avc, &sc->video_avcc)) {return -1;} |
| 77 | + print_timing("Video timming info from the decoder preparation", sc->avfc, sc->video_avcc, sc->video_avs); |
| 78 | + } else if (sc->avfc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { |
| 79 | + // keeping a pointer to the audio av stream |
| 80 | + sc->audio_avs = sc->avfc->streams[i]; |
| 81 | + sc->audio_index = i; |
| 82 | + |
| 83 | + if (fill_stream_info(sc->audio_avs, &sc->audio_avc, &sc->audio_avcc)) {return -1;} |
| 84 | + print_timing("Audio timming info from the decoder preparation", sc->avfc, sc->audio_avcc, sc->audio_avs); |
| 85 | + } else { |
| 86 | + logging("skipping streams other than audio and video"); |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + return 0; |
| 91 | +} |
| 92 | + |
| 93 | +int prepare_encoder(StreamingContext *sc, AVCodecParameters *decoder_par, AVRational input_framerate) { |
| 94 | + /* |
| 95 | + * Prepping the encoder - video stream to be transcoded |
| 96 | + */ |
| 97 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__encoding.html#gaa614ffc38511c104bdff4a3afa086d37 |
| 98 | + // Find a registered encoder with the specified name. |
| 99 | + sc->video_avc = avcodec_find_encoder_by_name("libx264"); |
| 100 | + if (!sc->video_avc) {logging("could not find the proper codec"); return -1;} |
| 101 | + |
| 102 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#gae80afec6f26df6607eaacf39b561c315 |
| 103 | + // Allocate an AVCodecContext and set its fields to default values. |
| 104 | + sc->video_avcc = avcodec_alloc_context3(sc->video_avc); |
| 105 | + if (!sc->video_avcc) {logging("could not allocated memory for codec context"); return -1;} |
| 106 | + |
| 107 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#gac7b282f51540ca7a99416a3ba6ee0d16 |
| 108 | + // Fill the codec context based on the values from the supplied codec parameters. |
| 109 | + if(avcodec_parameters_to_context(sc->video_avcc, decoder_par) < 0){logging("failed to copy codec parameters from the decoder"); return -1;} |
| 110 | + print_timing("Video encoder 1", sc->avfc, sc->video_avcc, sc->video_avs); |
| 111 | + |
| 112 | + av_opt_set(sc->video_avcc->priv_data, "preset", "slow", 0); |
| 113 | + //av_opt_set(sc->video_avcc->priv_data, "x264opts", "keyint=60:min-keyint=60:scenecut=-1", 0); |
| 114 | + |
| 115 | + AVDictionary *encoder_private_options = NULL; |
| 116 | + //av_dict_set(&encoder_private_options , "b", "2.0M", 0); |
| 117 | + |
| 118 | + sc->video_avcc->time_base = input_framerate; |
| 119 | + |
| 120 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#ga11f785a188d7d9df71621001465b0f1d |
| 121 | + // Initialize the AVCodecContext to use the given AVCodec. (does it resets the avcc?) |
| 122 | + if (avcodec_open2(sc->video_avcc, sc->video_avc, &encoder_private_options) < 0) {logging("could not open the codec"); return -1;} |
| 123 | + print_timing("Video encoder 2", sc->avfc, sc->video_avcc, sc->video_avs); |
| 124 | + |
| 125 | + sc->video_avs = avformat_new_stream(sc->avfc, NULL); |
| 126 | + //sc->video_avs->time_base = (AVRational){1, input_framerate}; |
| 127 | + |
| 128 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#ga6d02e640ccc12c783841ce51d09b9fa7 |
| 129 | + // Any allocated fields in dst are freed and replaced with newly allocated duplicates of the corresponding fields in src. |
| 130 | + avcodec_parameters_copy(sc->video_avs->codecpar, decoder_par); |
| 131 | + print_timing("Video timming info from the encoder preparation", sc->avfc, sc->video_avcc, sc->video_avs); |
| 132 | + print_timing("Video encoder 3", sc->avfc, sc->video_avcc, sc->video_avs); |
| 133 | + |
| 134 | + return 0; |
| 135 | +} |
| 136 | + |
| 137 | +int prepare_copy(AVFormatContext *avfc, AVStream **avs, AVCodecParameters *decoder_par) { |
| 138 | + *avs = avformat_new_stream(avfc, NULL); |
| 139 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#ga6d02e640ccc12c783841ce51d09b9fa7 |
| 140 | + // Any allocated fields in dst are freed and replaced with newly allocated duplicates of the corresponding fields in src. |
| 141 | + avcodec_parameters_copy((*avs)->codecpar, decoder_par); |
| 142 | + return 0; |
| 143 | +} |
| 144 | + |
| 145 | +int remux(AVPacket **pkt, AVFormatContext **avfc, AVRational decoder_tb, AVRational encoder_tb) { |
| 146 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__packet.html#gae5c86e4d93f6e7aa62ef2c60763ea67e |
| 147 | + // Convert valid timing fields (timestamps / durations) in a packet from one timebase to another. |
| 148 | + av_packet_rescale_ts(*pkt, decoder_tb, encoder_tb); |
| 149 | + |
| 150 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga37352ed2c63493c38219d935e71db6c1 |
| 151 | + // Write a packet to an output media file ensuring correct interleaving |
| 152 | + if (av_interleaved_write_frame(*avfc, *pkt) < 0) { logging("error while copying stream packet"); return -1; } |
| 153 | + av_packet_unref(*pkt); |
| 154 | + return 0; |
| 155 | +} |
| 156 | + |
| 157 | + |
| 158 | +int main(int argc, char *argv[]) |
| 159 | +{ |
| 160 | + StreamingParams sp = {0}; |
| 161 | + sp.copy_audio = 1; |
| 162 | + sp.copy_video = 1; |
| 163 | + |
| 164 | + StreamingContext *decoder = (StreamingContext*) calloc(1, sizeof(StreamingContext)); |
| 165 | + decoder->filename = argv[1]; |
| 166 | + |
| 167 | + StreamingContext *encoder = (StreamingContext*) calloc(1, sizeof(StreamingContext)); |
| 168 | + encoder->filename = argv[2]; |
| 169 | + |
| 170 | + /* |
| 171 | + * Opening the decodable media file |
| 172 | + */ |
| 173 | + if (open_media(decoder->filename, &decoder->avfc)) return -1; |
| 174 | + |
| 175 | + /* |
| 176 | + * Prepping the decoder |
| 177 | + */ |
| 178 | + if (prepare_decoder(decoder)) return -1; |
| 179 | + |
| 180 | + /* |
| 181 | + * Opening the output media file |
| 182 | + */ |
| 183 | + //https://www.ffmpeg.org/doxygen/trunk/avformat_8h.html#a0234fa1116af3c0a72edaa08a2ba304f |
| 184 | + // Allocate an AVFormatContext for an output format. |
| 185 | + avformat_alloc_output_context2(&encoder->avfc, NULL, NULL, encoder->filename); |
| 186 | + if (!encoder->avfc) {logging("could not allocate memory for output format");return -1;} |
| 187 | + |
| 188 | + /* |
| 189 | + * Prepping the encoder |
| 190 | + */ |
| 191 | + |
| 192 | + // for video |
| 193 | + if (!sp.copy_video) { |
| 194 | + // transcoding |
| 195 | + // TODO: |
| 196 | + } else { |
| 197 | + // just copying |
| 198 | + if (prepare_copy(encoder->avfc, &encoder->video_avs, decoder->video_avs->codecpar)) {return -1;} |
| 199 | + } |
| 200 | + |
| 201 | + // for audio |
| 202 | + if (!sp.copy_audio) { |
| 203 | + // transcoding |
| 204 | + // TODO: |
| 205 | + } else { |
| 206 | + // just copying |
| 207 | + if (prepare_copy(encoder->avfc, &encoder->audio_avs, decoder->audio_avs->codecpar)) {return -1;} |
| 208 | + } |
| 209 | + |
| 210 | + /* |
| 211 | + * Prepping the output media file |
| 212 | + */ |
| 213 | + if (encoder->avfc->oformat->flags & AVFMT_GLOBALHEADER) |
| 214 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavc__core.html#ga9ed82634c59b339786575827c47a8f68 |
| 215 | + // Place global headers in extradata instead of every keyframe. |
| 216 | + encoder->avfc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; |
| 217 | + |
| 218 | + if (!(encoder->avfc->oformat->flags & AVFMT_NOFILE)) { |
| 219 | + //https://www.ffmpeg.org/doxygen/trunk/aviobuf_8c.html#ab1b99c5b70aa59f15ab7cd4cbb40381e |
| 220 | + // Create and initialize a AVIOContext for accessing the resource indicated by url. |
| 221 | + if (avio_open(&encoder->avfc->pb, encoder->filename, AVIO_FLAG_WRITE) < 0) { |
| 222 | + logging("could not open the output file"); |
| 223 | + return -1; |
| 224 | + } |
| 225 | + } |
| 226 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga18b7b10bb5b94c4842de18166bc677cb |
| 227 | + // Allocate the stream private data and write the stream header to an output media file. |
| 228 | + if (avformat_write_header(encoder->avfc, NULL) < 0) {logging("an error occurred when opening output file"); return -1;} |
| 229 | + |
| 230 | + /* |
| 231 | + * Reading the streams packets from the input media file |
| 232 | + */ |
| 233 | + AVFrame *input_frame = av_frame_alloc(); |
| 234 | + if (!input_frame) {logging("failed to allocated memory for AVFrame"); return -1;} |
| 235 | + |
| 236 | + AVPacket *input_packet = av_packet_alloc(); |
| 237 | + if (!input_packet) {logging("failed to allocated memory for AVPacket"); return -1;} |
| 238 | + |
| 239 | + int response = 0; |
| 240 | + |
| 241 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#ga4fdb3084415a82e3810de6ee60e46a61 |
| 242 | + // Return the next frame of a stream. |
| 243 | + while (av_read_frame(decoder->avfc, input_packet) >= 0) |
| 244 | + { |
| 245 | + if (decoder->avfc->streams[input_packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { |
| 246 | + if (!sp.copy_video) { |
| 247 | + // transcoding |
| 248 | + // TODO |
| 249 | + } else { |
| 250 | + // just copying |
| 251 | + if (remux(&input_packet, &encoder->avfc, decoder->video_avs->time_base, encoder->video_avs->time_base)) return -1; |
| 252 | + } |
| 253 | + } else if (decoder->avfc->streams[input_packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { |
| 254 | + if (!sp.copy_audio) { |
| 255 | + // transcoding |
| 256 | + // TODO |
| 257 | + } else { |
| 258 | + // just copying |
| 259 | + if (remux(&input_packet, &encoder->avfc, decoder->audio_avs->time_base, encoder->audio_avs->time_base)) return -1; |
| 260 | + } |
| 261 | + } else { |
| 262 | + logging("ignoring all non video or audio packets"); |
| 263 | + } |
| 264 | + } |
| 265 | + //https://www.ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga7f14007e7dc8f481f054b21614dfec13 |
| 266 | + // Write the stream trailer to an output media file and free the file private data. |
| 267 | + av_write_trailer(encoder->avfc); |
| 268 | + |
| 269 | + // TODO: we should free everything! |
| 270 | + return 0; |
| 271 | +} |
| 272 | + |
0 commit comments