20.FFmpeg学习笔记 - 带编码的视频复用器(mux)

本篇文章写一个视频复用器。输入一个pcm文件和一个yuv文件。pcm文件格式是f32le,单声道,采样率是48000。yuv文件格式是yuv420p,848x400。

读入两个文件,分别进行音频和视频编码,然后mux成目标文件,比如xxx.mp4。

代码中选择了编码2声道的音频,由于输入的pcm是单声道,所以代码中简单复制了pcm的数据到frame->data[1]中。

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>



typedef struct OutputStream {
    AVStream *st;
    AVCodecContext *enc_ctx;
    /* 下一帧pts */
    int64_t next_pts;
    AVFrame *frame;
} OutputStream;

static AVFormatContext *ofmt_ctx;
static OutputStream video_st = {0}, audio_st = {0};
static FILE *in_file_v, *in_file_a;


static void log_packet(const AVPacket *pkt)
{
    AVRational *time_base = &ofmt_ctx->streams[pkt->stream_index]->time_base;
    
    printf("pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           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);
}


static void setup_video_stream()
{
    int ret;
    
    AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        printf("could not find %s encoder\n", avcodec_get_name(AV_CODEC_ID_H264));
        exit(1);
    }
    //创建流
    video_st.st = avformat_new_stream(ofmt_ctx, NULL);
    if (!video_st.st) {
        printf("avformat_new_stream fail \n");
        exit(1);
    }
    //打开codec context
    video_st.enc_ctx = avcodec_alloc_context3(codec);
    if (!video_st.enc_ctx) {
        printf("avcodec_alloc_context3 fail \n");
        exit(1);
    }
    
    //设置编码参数
    video_st.enc_ctx->bit_rate = 400000;
    video_st.enc_ctx->width = 848;
    video_st.enc_ctx->height = 480;
    video_st.enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    video_st.st->time_base = (AVRational){1, 25};
    video_st.enc_ctx->time_base = video_st.st->time_base;
    video_st.enc_ctx->gop_size = 12;
    video_st.enc_ctx->max_b_frames = 2;
    
    //对于mp4/mkv/flv等格式,他们要求有global header,对于ts则没有
    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
        video_st.enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    
    ret= avcodec_open2(video_st.enc_ctx, codec, NULL);
    if (ret < 0) {
        printf("could not open codec\n");
        exit(1);
    }
    
    video_st.frame = av_frame_alloc();
    if (!video_st.frame) {
        exit(1);
    }
    video_st.frame->format = video_st.enc_ctx->pix_fmt;
    video_st.frame->width = video_st.enc_ctx->width;
    video_st.frame->height = video_st.enc_ctx->height;
    
    //创建缓存,用于存放待编码数据
    ret = av_frame_get_buffer(video_st.frame, 0);
    if (ret < 0) {
        printf("av_frame_get_buffer fail\n");
        exit(1);
    }
    
    ret = avcodec_parameters_from_context(video_st.st->codecpar, video_st.enc_ctx);
    if (ret < 0) {
        printf("avcodec_parameters_from_context fail\n");
        exit(1);
    }
}

static void setup_audio_stream()
{
    int ret, nb_samples;
    
    AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    if (!codec) {
        printf("could not find %s encoder\n", avcodec_get_name(AV_CODEC_ID_AAC));
        exit(1);
    }
    //创建流
    audio_st.st = avformat_new_stream(ofmt_ctx, NULL);
    if (!audio_st.st) {
        printf("avformat_new_stream fail \n");
        exit(1);
    }
    //打开codec context
    audio_st.enc_ctx = avcodec_alloc_context3(codec);
    if (!audio_st.enc_ctx) {
        printf("avcodec_alloc_context3 fail \n");
        exit(1);
    }
    
    //设置编码参数
    audio_st.enc_ctx->bit_rate = 64000;
    audio_st.enc_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
    audio_st.enc_ctx->channels = av_get_channel_layout_nb_channels(audio_st.enc_ctx->channel_layout);
    audio_st.enc_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
    audio_st.enc_ctx->sample_rate = 48000;
    audio_st.st->time_base = (AVRational){1, audio_st.enc_ctx->sample_rate};
    audio_st.enc_ctx->time_base = audio_st.st->time_base;
    
    //对于mp4/mkv/flv等格式,他们要求有global header,对于ts则没有
    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
        audio_st.enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    
    ret= avcodec_open2(audio_st.enc_ctx, codec, NULL);
    if (ret < 0) {
        printf("could not open codec\n");
        exit(1);
    }
    
    audio_st.frame = av_frame_alloc();
    if (!audio_st.frame) {
        exit(1);
    }
    
    if (audio_st.enc_ctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
        nb_samples = 10000;
    else
        nb_samples = audio_st.enc_ctx->frame_size;
    
    audio_st.frame->format = audio_st.enc_ctx->sample_fmt;
    audio_st.frame->channel_layout = audio_st.enc_ctx->channel_layout;
    audio_st.frame->nb_samples = nb_samples;
    
    //创建缓存,用于存放待编码数据
    ret = av_frame_get_buffer(audio_st.frame, 0);
    if (ret < 0) {
        printf("av_frame_get_buffer fail\n");
        exit(1);
    }
    
    ret = avcodec_parameters_from_context(audio_st.st->codecpar, audio_st.enc_ctx);
    if (ret < 0) {
        printf("avcodec_parameters_from_context fail\n");
        exit(1);
    }
    
}

static void encode_video(AVFrame *frame)
{
    int ret;
    AVPacket pkt = {0};
    av_init_packet(&pkt);
    
    /* 编码 */
    ret = avcodec_send_frame(video_st.enc_ctx, frame);
    if (ret < 0) {
        fprintf(stderr, "Error sending a frame for encoding\n");
        exit(1);
    }
    
    while (ret >= 0) {
        ret = avcodec_receive_packet(video_st.enc_ctx, &pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during encoding\n");
            exit(1);
        }
        
        av_packet_rescale_ts(&pkt, video_st.enc_ctx->time_base, video_st.st->time_base);
        pkt.stream_index = video_st.st->index;
        log_packet(&pkt);
        //写入文件
        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            printf("av_interleaved_write_frame fail\n");
            exit(1);
        }
    }
}


//返回值:0编码未结束,1结束
static int write_video_frame(void)
{
    int ret;
    int width = video_st.frame->width;
    int height = video_st.frame->height;
    uint8_t yuv[width * height * 3 / 2];
    
    if (!feof(in_file_v)) {
        
        ret = av_frame_make_writable(video_st.frame);
        if (ret < 0)
            exit(1);
        
        //清空
        memset(yuv, 0, width * height * 3 / 2);
        
        //读取Y、U、V
        fread(yuv, 1, width * height * 3 / 2, in_file_v);
        
        //设置Y、U、V数据到frame
        /* Y */
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                video_st.frame->data[0][y * video_st.frame->linesize[0] + x] = yuv[y * width + x];
            }
        }
        /* U and V */
        for (int y = 0; y < height/2; y++) {
            for (int x = 0; x < width/2; x++) {
                video_st.frame->data[1][y * video_st.frame->linesize[1] + x] = yuv[width * height + y * width / 2 + x];
                video_st.frame->data[2][y * video_st.frame->linesize[2] + x] = yuv[width * height * 5 / 4 + y * width / 2 + x];
            }
        }
        
        //设置pts
        video_st.frame->pts = video_st.next_pts++;
        
        //编码
        encode_video(video_st.frame);
        
        return 0;
        
    }else {
        
        /* flush */
        encode_video(NULL);
        return 1;
        
    }
    
}


static void encode_audio(AVFrame *frame)
{
    int ret;
    AVPacket pkt = {0};
    av_init_packet(&pkt);
    
    /* 编码 */
    ret = avcodec_send_frame(audio_st.enc_ctx, frame);
    if (ret < 0) {
        fprintf(stderr, "Error sending a frame for encoding\n");
        exit(1);
    }
    
    while (ret >= 0) {
        ret = avcodec_receive_packet(audio_st.enc_ctx, &pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during encoding\n");
            exit(1);
        }
        
        av_packet_rescale_ts(&pkt, audio_st.enc_ctx->time_base, audio_st.st->time_base);
        pkt.stream_index = audio_st.st->index;
        log_packet(&pkt);
        //写入文件
        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            printf("av_interleaved_write_frame fail\n");
            exit(1);
        }
    }
}

//返回值:0编码未结束,1结束
static int write_audio_frame(void)
{
    int ret;
    int bytes_per_sample = av_get_bytes_per_sample(audio_st.enc_ctx->sample_fmt);
    
    if (!feof(in_file_a)) {
        ret = av_frame_make_writable(audio_st.frame);
        if (ret < 0)
            exit(1);
        fread(audio_st.frame->data[0], 1, audio_st.frame->nb_samples * bytes_per_sample, in_file_a);
        memcpy(audio_st.frame->data[1], audio_st.frame->data[0], audio_st.frame->nb_samples * bytes_per_sample);
        
        //设置pts
        audio_st.frame->pts = audio_st.next_pts;
        audio_st.next_pts += audio_st.frame->nb_samples;
        
        encode_audio(audio_st.frame);
        
        return 0;

    }else {
        /* flush */
        encode_audio(NULL);
        return 1;
    }
}

static void encode_and_mux()
{
    const char *in_filename_v = "/Users/zhw/Desktop/resource/sintel_yuv420p_848x480.yuv";
    const char *in_filename_a = "/Users/zhw/Desktop/resource/sintel_f32le_left_1_48000.pcm";
    const char *out_filename = "/Users/zhw/Desktop/sintel.mp4";
    AVOutputFormat *ofmt;
    int ret;
    //视频,音频是否编码结束
    int encode_video = 1, encode_audio = 1;
    
    in_file_v = fopen(in_filename_v, "rb");
    if (!in_file_v) {
        printf("file %s not exist\n", in_filename_v);
        exit(1);
    }
    
    in_file_a = fopen(in_filename_a, "rb");
    if (!in_file_a) {
        printf("file %s not exist\n", in_filename_a);
        exit(1);
    }
    
    //创建输出context
    ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
    if (ret < 0) {
        printf("avformat_alloc_output_context2 fail\n");
        exit(1);
    }
    
    ofmt = ofmt_ctx->oformat;
    
    //设置视频流
    setup_video_stream();
    
    //设置音频流
    setup_audio_stream();
    
    //打印信息
    av_dump_format(ofmt_ctx, 0, out_filename, 1);
    
    /* 打开输出文件 */
    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            printf("avio_open fail\n");
            exit(1);
        }
    }
    
    //写文件头
    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0) {
        printf("avformat_write_header fail \n");
        exit(1);
    }
    
    while (encode_video || encode_audio) {
        if (encode_video &&
            (!encode_audio || av_compare_ts(video_st.next_pts, video_st.enc_ctx->time_base,
                                            audio_st.next_pts, audio_st.enc_ctx->time_base) <= 0)) {
            encode_video = !write_video_frame();
        } else {
            encode_audio = !write_audio_frame();
        }
    }
    
    av_write_trailer(ofmt_ctx);
    
    //free
    avcodec_free_context(&video_st.enc_ctx);
    av_frame_free(&video_st.frame);
    avcodec_free_context(&audio_st.enc_ctx);
    av_frame_free(&audio_st.frame);
    if (!(ofmt->flags & AVFMT_NOFILE))
        avio_closep(&ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);

    
}

猜你喜欢

转载自blog.csdn.net/whoyouare888/article/details/94877926