zoukankan      html  css  js  c++  java
  • ffmpeg 编码(视屏)

    分析ffmpeg_3.3.2 muxing 

    1:分析主函数,代码如下:

      

      1 int main(int argc, char **argv)
      2 {
      3     OutputStream video_st = { 0 }, audio_st = { 0 };
      4     const char *filename;
      5     AVOutputFormat *fmt;
      6     AVFormatContext *oc;
      7     AVCodec *audio_codec, *video_codec;
      8     int ret;
      9     int have_video = 0, have_audio = 0;
     10     int encode_video = 0, encode_audio = 0;
     11     AVDictionary *opt = NULL;
     12     int i;
     13 
     14     /* Initialize libavcodec, and register all codecs and formats. */
     15     av_register_all();
     16 
     17     if (argc < 2) {
     18         printf("usage: %s output_file
    "
     19                "API example program to output a media file with libavformat.
    "
     20                "This program generates a synthetic audio and video stream, encodes and
    "
     21                "muxes them into a file named output_file.
    "
     22                "The output format is automatically guessed according to the file extension.
    "
     23                "Raw images can also be output by using '%%d' in the filename.
    "
     24                "
    ", argv[0]);
     25         return 1;
     26     }
     27 
     28     filename = argv[1];
     29     for (i = 2; i+1 < argc; i+=2) {
     30         if (!strcmp(argv[i], "-flags") || !strcmp(argv[i], "-fflags"))
     31             av_dict_set(&opt, argv[i]+1, argv[i+1], 0);
     32     }
     33 
     34     /* allocate the output media context */
     35     avformat_alloc_output_context2(&oc, NULL, NULL, filename);
     36     if (!oc) {
     37         printf("Could not deduce output format from file extension: using MPEG.
    ");
     38         avformat_alloc_output_context2(&oc, NULL, "mpeg", filename);
     39     }
     40     if (!oc)
     41         return 1;
     42 
     43     fmt = oc->oformat;
     44 
     45     /* Add the audio and video streams using the default format codecs
     46      * and initialize the codecs. */
     47     if (fmt->video_codec != AV_CODEC_ID_NONE) {
     48         add_stream(&video_st, oc, &video_codec, fmt->video_codec);
     49         have_video = 1;
     50         encode_video = 1;
     51     }
     52     if (fmt->audio_codec != AV_CODEC_ID_NONE) {
     53         add_stream(&audio_st, oc, &audio_codec, fmt->audio_codec);
     54         have_audio = 1;
     55         encode_audio = 1;
     56     }
     57 
     58     /* Now that all the parameters are set, we can open the audio and
     59      * video codecs and allocate the necessary encode buffers. */
     60     if (have_video)
     61         open_video(oc, video_codec, &video_st, opt);
     62 
     63     if (have_audio)
     64         open_audio(oc, audio_codec, &audio_st, opt);
     65 
     66     av_dump_format(oc, 0, filename, 1);
     67 
     68     /* open the output file, if needed */
     69     if (!(fmt->flags & AVFMT_NOFILE)) {
     70         ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
     71         if (ret < 0) {
     72             fprintf(stderr, "Could not open '%s': %s
    ", filename,
     73                     av_err2str(ret));
     74             return 1;
     75         }
     76     }
     77 
     78     /* Write the stream header, if any. */
     79     ret = avformat_write_header(oc, &opt);
     80     if (ret < 0) {
     81         fprintf(stderr, "Error occurred when opening output file: %s
    ",
     82                 av_err2str(ret));
     83         return 1;
     84     }
     85 
     86     while (encode_video || encode_audio) {
     87         /* select the stream to encode */
     88         if (encode_video &&
     89             (!encode_audio || av_compare_ts(video_st.next_pts, video_st.enc->time_base,
     90                                             audio_st.next_pts, audio_st.enc->time_base) <= 0)) {
     91             encode_video = !write_video_frame(oc, &video_st);
     92         } else {
     93             encode_audio = !write_audio_frame(oc, &audio_st);
     94         }
     95     }
     96 
     97     /* Write the trailer, if any. The trailer must be written before you
     98      * close the CodecContexts open when you wrote the header; otherwise
     99      * av_write_trailer() may try to use memory that was freed on
    100      * av_codec_close(). */
    101     av_write_trailer(oc);
    102 
    103     /* Close each codec. */
    104     if (have_video)
    105         close_stream(oc, &video_st);
    106     if (have_audio)
    107         close_stream(oc, &audio_st);
    108 
    109     if (!(fmt->flags & AVFMT_NOFILE))
    110         /* Close the output file. */
    111         avio_closep(&oc->pb);
    112 
    113     /* free the stream */
    114     avformat_free_context(oc);
    115 
    116     return 0;
    117 }

    2:首先,定义了一个自定义结构体OutputStream,分别来代表一个音频,视频流。

      

     1 // a wrapper around a single output AVStream
     2 typedef struct OutputStream {
     3     AVStream *st;
     4     AVCodecContext *enc;
     5 
     6     /* pts of the next frame that will be generated */
     7     int64_t next_pts;
     8     int samples_count;
     9 
    10     AVFrame *frame;
    11     AVFrame *tmp_frame;
    12 
    13     float t, tincr, tincr2;
    14 
    15     struct SwsContext *sws_ctx;
    16     struct SwrContext *swr_ctx;
    17 } OutputStream;

      st是创建的视频,音频流,enc为编码器上下文等等。

    3:主函数中首先使用av_register_all()注册所有编码器和格式.

      创建一个输出媒体上下文:avformat_alloc_output_context2(&oc,NULL,NULL,filename);

      创建一个视屏轨道和音频轨道:add_stream(&video_st, oc, &video_codec, fmt->video_codec);

    4:分析add_stream函数:

      

     1 static void add_stream(OutputStream *ost, AVFormatContext *oc,
     2                        AVCodec **codec,
     3                        enum AVCodecID codec_id)
     4 {
     5     AVCodecContext *c;
     6     int i;
     7 
     8     /* find the encoder */
     9     *codec = avcodec_find_encoder(codec_id);
    10     if (!(*codec)) {
    11         fprintf(stderr, "Could not find encoder for '%s'
    ",
    12                 avcodec_get_name(codec_id));
    13         exit(1);
    14     }
    15 
    16     ost->st = avformat_new_stream(oc, NULL);
    17     if (!ost->st) {
    18         fprintf(stderr, "Could not allocate stream
    ");
    19         exit(1);
    20     }
    21     ost->st->id = oc->nb_streams-1;
    22     c = avcodec_alloc_context3(*codec);
    23     if (!c) {
    24         fprintf(stderr, "Could not alloc an encoding context
    ");
    25         exit(1);
    26     }
    27     ost->enc = c;
    28 
    29     switch ((*codec)->type) {
    30     case AVMEDIA_TYPE_AUDIO:
    31         c->sample_fmt  = (*codec)->sample_fmts ?
    32             (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
    33         c->bit_rate    = 64000;
    34         c->sample_rate = 44100;
    35         if ((*codec)->supported_samplerates) {
    36             c->sample_rate = (*codec)->supported_samplerates[0];
    37             for (i = 0; (*codec)->supported_samplerates[i]; i++) {
    38                 if ((*codec)->supported_samplerates[i] == 44100)
    39                     c->sample_rate = 44100;
    40             }
    41         }
    42         c->channels        = av_get_channel_layout_nb_channels(c->channel_layout);
    43         c->channel_layout = AV_CH_LAYOUT_STEREO;
    44         if ((*codec)->channel_layouts) {
    45             c->channel_layout = (*codec)->channel_layouts[0];
    46             for (i = 0; (*codec)->channel_layouts[i]; i++) {
    47                 if ((*codec)->channel_layouts[i] == AV_CH_LAYOUT_STEREO)
    48                     c->channel_layout = AV_CH_LAYOUT_STEREO;
    49             }
    50         }
    51         c->channels        = av_get_channel_layout_nb_channels(c->channel_layout);
    52         ost->st->time_base = (AVRational){ 1, c->sample_rate };
    53         break;
    54 
    55     case AVMEDIA_TYPE_VIDEO:
    56         c->codec_id = codec_id;
    57 
    58         c->bit_rate = 400000;
    59         /* Resolution must be a multiple of two. */
    60         c->width    = 352;
    61         c->height   = 288;
    62         /* timebase: This is the fundamental unit of time (in seconds) in terms
    63          * of which frame timestamps are represented. For fixed-fps content,
    64          * timebase should be 1/framerate and timestamp increments should be
    65          * identical to 1. */
    66         ost->st->time_base = (AVRational){ 1, STREAM_FRAME_RATE };
    67         c->time_base       = ost->st->time_base;
    68 
    69         c->gop_size      = 12; /* emit one intra frame every twelve frames at most */
    70         c->pix_fmt       = STREAM_PIX_FMT;
    71         if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
    72             /* just for testing, we also add B-frames */
    73             c->max_b_frames = 2;
    74         }
    75         if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
    76             /* Needed to avoid using macroblocks in which some coeffs overflow.
    77              * This does not happen with normal video, it just happens here as
    78              * the motion of the chroma plane does not match the luma plane. */
    79             c->mb_decision = 2;
    80         }
    81     break;
    82 
    83     default:
    84         break;
    85     }
    86 
    87     /* Some formats want stream headers to be separate. */
    88     if (oc->oformat->flags & AVFMT_GLOBALHEADER)
    89         c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    90 }

    5:创建完音频,视屏轨道后,则打开视频,音频编码器,准备编码数据 open_video(oc, video_codec, &video_st, opt);

      

     1 static void open_video(AVFormatContext *oc, AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg)
     2 {
     3     int ret;
     4     AVCodecContext *c = ost->enc;
     5     AVDictionary *opt = NULL;
     6 
     7     av_dict_copy(&opt, opt_arg, 0);
     8 
     9     /* open the codec */
    10     ret = avcodec_open2(c, codec, &opt);
    11     av_dict_free(&opt);
    12     if (ret < 0) {
    13         fprintf(stderr, "Could not open video codec: %s
    ", av_err2str(ret));
    14         exit(1);
    15     }
    16 
    17     /* allocate and init a re-usable frame */
    18     ost->frame = alloc_picture(c->pix_fmt, c->width, c->height);
    19     if (!ost->frame) {
    20         fprintf(stderr, "Could not allocate video frame
    ");
    21         exit(1);
    22     }
    23 
    24     /* If the output format is not YUV420P, then a temporary YUV420P
    25      * picture is needed too. It is then converted to the required
    26      * output format. */
    27     ost->tmp_frame = NULL;
    28     if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
    29         ost->tmp_frame = alloc_picture(AV_PIX_FMT_YUV420P, c->width, c->height);
    30         if (!ost->tmp_frame) {
    31             fprintf(stderr, "Could not allocate temporary picture
    ");
    32             exit(1);
    33         }
    34     }
    35 
    36     /* copy the stream parameters to the muxer */
    37     ret = avcodec_parameters_from_context(ost->st->codecpar, c);
    38     if (ret < 0) {
    39         fprintf(stderr, "Could not copy the stream parameters
    ");
    40         exit(1);
    41     }
    42 }

    注意:static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height),创建一个指定格式,宽和高的帧。

      tmp_frame:如果格式不是YUV420P,则该帧用于转换格式。否则直接使用frame则可。

     1 static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
     2 {
     3     AVFrame *picture;
     4     int ret;
     5 
     6     picture = av_frame_alloc();
     7     if (!picture)
     8         return NULL;
     9 
    10     picture->format = pix_fmt;
    11     picture->width  = width;
    12     picture->height = height;
    13 
    14     /* allocate the buffers for the frame data */
    15     ret = av_frame_get_buffer(picture, 32);
    16     if (ret < 0) {
    17         fprintf(stderr, "Could not allocate frame data.
    ");
    18         exit(1);
    19     }
    20 
    21     return picture;
    22 }

    6:打开输出文件:

      

    1 if (!(fmt->flags & AVFMT_NOFILE)) {
    2         ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
    3         if (ret < 0) {
    4             fprintf(stderr, "Could not open '%s': %s
    ", filename,
    5                     av_err2str(ret));
    6             return 1;
    7         }
    8     }

    7:写入文件头

      

    1 ret = avformat_write_header(oc, &opt);
    2     if (ret < 0) {
    3         fprintf(stderr, "Error occurred when opening output file: %s
    ",
    4                 av_err2str(ret));
    5         return 1;
    6     }

    8:循环编码写入数据

      

     1 while (encode_video || encode_audio) {
     2         /* select the stream to encode */
     3         if (encode_video &&
     4             (!encode_audio || av_compare_ts(video_st.next_pts, video_st.enc->time_base,
     5                                             audio_st.next_pts, audio_st.enc->time_base) <= 0)) {
     6             encode_video = !write_video_frame(oc, &video_st);
     7         } else {
     8             encode_audio = !write_audio_frame(oc, &audio_st);
     9         }
    10     }

      分析static int write_video_frame(AVFormatContext *oc, OutputStream *ost),编码一帧视屏,并且送入muxer(复合器),编码完成返回1,否则返回0

      

    static int write_video_frame(AVFormatContext *oc, OutputStream *ost)
    {
        int ret;
        AVCodecContext *c;
        AVFrame *frame;
        int got_packet = 0;
        AVPacket pkt = { 0 };
    
        c = ost->enc;
    
        frame = get_video_frame(ost);
    
        av_init_packet(&pkt);
    
        /* encode the image */
        ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);
        if (ret < 0) {
            fprintf(stderr, "Error encoding video frame: %s
    ", av_err2str(ret));
            exit(1);
        }
    
        if (got_packet) {
            ret = write_frame(oc, &c->time_base, ost->st, &pkt);
        } else {
            ret = 0;
        }
    
        if (ret < 0) {
            fprintf(stderr, "Error while writing video frame: %s
    ", av_err2str(ret));
            exit(1);
        }
    
        return (frame || got_packet) ? 0 : 1;
    }

      创建一帧数据:frame = get_video_frame(ost);

      

     1 static AVFrame *get_video_frame(OutputStream *ost)
     2 {
     3     AVCodecContext *c = ost->enc;
     4 
     5     /* check if we want to generate more frames */
     6     if (av_compare_ts(ost->next_pts, c->time_base,
     7                       STREAM_DURATION, (AVRational){ 1, 1 }) >= 0)
     8         return NULL;
     9 
    10     /* when we pass a frame to the encoder, it may keep a reference to it
    11      * internally; make sure we do not overwrite it here */
    12     if (av_frame_make_writable(ost->frame) < 0)
    13         exit(1);
    14 
    15     if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
    16         /* as we only generate a YUV420P picture, we must convert it
    17          * to the codec pixel format if needed */
    18         if (!ost->sws_ctx) {
    19             ost->sws_ctx = sws_getContext(c->width, c->height,
    20                                           AV_PIX_FMT_YUV420P,
    21                                           c->width, c->height,
    22                                           c->pix_fmt,
    23                                           SCALE_FLAGS, NULL, NULL, NULL);
    24             if (!ost->sws_ctx) {
    25                 fprintf(stderr,
    26                         "Could not initialize the conversion context
    ");
    27                 exit(1);
    28             }
    29         }
    30         fill_yuv_image(ost->tmp_frame, ost->next_pts, c->width, c->height);
    31         sws_scale(ost->sws_ctx,
    32                   (const uint8_t * const *)ost->tmp_frame->data, ost->tmp_frame->linesize,
    33                   0, c->height, ost->frame->data, ost->frame->linesize);
    34     } else {
    35         fill_yuv_image(ost->frame, ost->next_pts, c->width, c->height);
    36     }
    37 
    38     ost->frame->pts = ost->next_pts++;
    39 
    40     return ost->frame;
    41 }

      使用static void fill_yuv_image(AVFrame *pict, int frame_index, int width, int height)填充一帧数据。

      

     1 static void fill_yuv_image(AVFrame *pict, int frame_index,
     2                            int width, int height)
     3 {
     4     int x, y, i;
     5 
     6     i = frame_index;
     7 
     8     /* Y */
     9     for (y = 0; y < height; y++)
    10         for (x = 0; x < width; x++)
    11             pict->data[0][y * pict->linesize[0] + x] = x + y + i * 3;
    12 
    13     /* Cb and Cr */
    14     for (y = 0; y < height / 2; y++) {
    15         for (x = 0; x < width / 2; x++) {
    16             pict->data[1][y * pict->linesize[1] + x] = 128 + y + i * 2;
    17             pict->data[2][y * pict->linesize[2] + x] = 64 + x + i * 5;
    18         }
    19     }
    20 }

      当编码成功后,使用自定义函数 write_frame(oc, &c->time_base, ost->st, &pkt),将一帧数据包写入上下文。

      

     1 static int write_frame(AVFormatContext *fmt_ctx, const AVRational *time_base, AVStream *st, AVPacket *pkt)
     2 {
     3     /* rescale output packet timestamp values from codec to stream timebase */
     4     av_packet_rescale_ts(pkt, *time_base, st->time_base);
     5     pkt->stream_index = st->index;
     6 
     7     /* Write the compressed frame to the media file. */
     8     log_packet(fmt_ctx, pkt);
     9     return av_interleaved_write_frame(fmt_ctx, pkt);
    10 }

    9:写尾部,尾部必须在关闭编码器上下文之前写入:

      

    1 av_write_trailer(oc);

    10:最后编码每个编码器和输出文件,释放上下文

      

     1 /* Close each codec. */
     2     if (have_video)
     3         close_stream(oc, &video_st);
     4     if (have_audio)
     5         close_stream(oc, &audio_st);
     6 
     7     if (!(fmt->flags & AVFMT_NOFILE))
     8         /* Close the output file. */
     9         avio_closep(&oc->pb);
    10 
    11     /* free the stream */
    12     avformat_free_context(oc);

    11:close_stream的实现如下:

      

    1 static void close_stream(AVFormatContext *oc, OutputStream *ost)
    2 {
    3     avcodec_free_context(&ost->enc);
    4     av_frame_free(&ost->frame);
    5     av_frame_free(&ost->tmp_frame);
    6     sws_freeContext(ost->sws_ctx);
    7     swr_free(&ost->swr_ctx);
    8 }
    作者:长风 Email:844064492@qq.com QQ群:607717453 Git:https://github.com/zhaohu19910409Dz 开源项目:https://github.com/OriginMEK/MEK 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 感谢您的阅读。如果觉得有用的就请各位大神高抬贵手“推荐一下”吧!你的精神支持是博主强大的写作动力。 如果觉得我的博客有意思,欢迎点击首页左上角的“+加关注”按钮关注我!
  • 相关阅读:
    BZOJ1527 : [POI2005]Pun-point
    2016-2017 ACM-ICPC Southwestern European Regional Programming Contest (SWERC 2016)
    2016-2017 ACM-ICPC Northwestern European Regional Programming Contest (NWERC 2016)
    NAIPC-2016
    BZOJ2498 : Xavier is Learning to Count
    ACM ICPC Vietnam National Second Round
    XVI Open Cup named after E.V. Pankratiev. GP of Ukraine
    XVI Open Cup named after E.V. Pankratiev. GP of Peterhof
    HDU5509 : Pattern String
    BZOJ4583 : 购物
  • 原文地址:https://www.cnblogs.com/zhaohu/p/7271524.html
Copyright © 2011-2022 走看看