四.ffmpeg对yuv数据进行h264编码

发布于:2025-03-23 ⋅ 阅读:(17) ⋅ 点赞:(0)

一.windows

1.什么是h264?

2.h264编码流程

3.主要整体代码

int open_coder(AVCodecContext **codec_ctx)
{
    // 编码器
    const AVCodec *codex = avcodec_find_encoder_by_name("libfdk_aac");
    // codex->capabilities = AV_CODEC_CAP_VARIABLE_FRAME_SIZE;
    if (!codex)
    {
        fprintf(stderr, "Codec not found\n");
        return -1;
    }
    // 编码器上下文
    *codec_ctx = avcodec_alloc_context3(codex);
    (*codec_ctx)->sample_fmt = AV_SAMPLE_FMT_S16;       // 采样大小
    (*codec_ctx)->channel_layout = AV_CH_LAYOUT_STEREO; //
    (*codec_ctx)->channels = 2;                         // 声道数
    (*codec_ctx)->sample_rate = 44100;                  // 采样率
    (*codec_ctx)->bit_rate = 0;                         // AAC 128k;AAC HE 64k; AAC_HE V2:32K
    // codec_ctx->profile = FF_PROFILE_AAC_HE_V2;       // 用哪个AAC
    if (avcodec_open2(*codec_ctx, codex, NULL) < 0)
    {
        fprintf(stderr, "failed avcodec_open2 \n");
        return -1;
    }
    return 0;
}
int encode(AVCodecContext *codec_ctx, AVFrame *avframe, AVPacket *outpkt, FILE *outfile)
{
    // printf("coder:%d\n", codec_ctx->codec->pix_fmts);

    // printf("sned frame to encoder ,pts=%lld\n", avframe->pts);
    int ret = avcodec_send_frame(codec_ctx, avframe);
    if (ret < 0)
    {
        fprintf(stderr, "Error sending frame to encoder\n");
        return -1;
    }

    while (ret >= 0)
    {
        // 获取编码后的音频数据
        // printf("pre-avcodec_receive_packet\n");
        ret = avcodec_receive_packet(codec_ctx, outpkt);
        // printf("avcodec_receive_packet:%d\n", ret);
        // 如果编码器数据不足时会返回  EAGAIN,或者到数据尾时会返回 AVERROR_EOF
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        {
            return 0;
        }
        else if (ret < 0)
        {
            printf("Error, Failed to encode!\n");
            exit(1);
        }
        printf("fwrite - outpkt.size: %d \n", outpkt->size);
        fwrite(outpkt->data, 1, outpkt->size, outfile);
        fflush(outfile);
        av_packet_unref(outpkt);
    }
    return 0;
}

int read_video()
{
    int ret = 0;
    char errors[1024];
    AVFormatContext *fmt_ctx = NULL;
    AVDictionary *options = NULL;
    AVAudioFifo *fifo = nullptr;
    // FILE *outfile = fopen("./out.yuv", "wb+");
    FILE *outfile_yuv = fopen("./out1.yuv", "wb+");
    FILE *outfile_h264 = fopen("./out2.h264", "wb+");
    if (outfile_yuv == nullptr)
    {
        printf("filed open out file\n");
    }

    AVPacket pkt;
    av_init_packet(&pkt);
    AVPacket *outpkt = av_packet_alloc();
    av_init_packet(outpkt);
    int frame_count = 0;

    // 找到采集工具
    const AVInputFormat *iformat = av_find_input_format("dshow");
    if (iformat == NULL)
    {
        printf("AVInputFormat find failed \n");
        return -1;
    }

    // 打开视频设备
    av_dict_set(&options, "video_size", "1280x720", 0);
    av_dict_set(&options, "framerate", "10", 0);
    av_dict_set(&options, "pixel_format", "yuyv422", 0);
    av_dict_set(&options, "rtbufsize", "20M", 0);
    ret = open_dev(&fmt_ctx, "video=Integrated Camera", iformat, &options);

    AVCodecContext *codex_ctx;
    ret = open_encoder(1280, 720, &codex_ctx);
    if (ret != 0)
    {
        printf("failed open codecx\n");
        return -1;
    }
    // 初始化格式转换上下文
    struct SwsContext *sws_ctx = sws_getContext(
        1280, 720, AV_PIX_FMT_YUYV422, // 输入图像的宽度、高度和像素格式
        1280, 720, AV_PIX_FMT_YUV420P, // 输出图像的宽度、高度和像素格式
        SWS_BILINEAR,                  // 转换算法
        NULL, NULL, NULL               // 额外参数
    );
    AVFrame *frame = av_frame_alloc();
    AVFrame *outFrame = av_frame_alloc();
    // 设置 YUV 422P 帧
    frame->format = AV_PIX_FMT_YUYV422;
    frame->width = 1280;
    frame->height = 720;
    // 设置 YUV 420P 帧
    outFrame->format = AV_PIX_FMT_YUV420P;
    outFrame->width = 1280;
    outFrame->height = 720;

    // 分配数据
    ret = av_image_alloc(frame->data, frame->linesize, 1280, 720, AV_PIX_FMT_YUYV422, 1);
    if (ret < 0)
    {
        fprintf(stderr, "无法分配图像数据\n");
        return -1;
    }
    ret = av_image_alloc(outFrame->data, outFrame->linesize, 1280, 720, AV_PIX_FMT_YUV420P, 1);
    if (ret < 0)
    {
        fprintf(stderr, "无法分配图像数据\n");
        return -1;
    }
    int count_ = 0;

    // 读取数据包并解码
    size_t written_y = 0, written_u = 0, written_v = 0;
    while (av_read_frame(fmt_ctx, &pkt) >= 0 && count_++ < 100)
    {
        // fwrite(pkt.data, 1, pkt.size, outfile_yuv);
        // fflush(outfile_yuv);
        memcpy(frame->data[0], pkt.data, 1280 * 720 * 2);
        fwrite(pkt.data, 1, pkt.size, outfile_yuv);
        fflush(outfile_yuv);
        // frame->pts = count_;
        // printf("pkt-size:%d , outframe->format:%d\n", pkt.size, frame->format);
        sws_scale_frame(sws_ctx, outFrame, frame);
        outFrame->pts = count_;
        // printf("pkt-size:%d , outframe->format:%d\n", pkt.size, outFrame->format);
        // // // yuv420写入文件
        // write_yuv_data(outFrame, outfile_yuv);
        // av_packet_unref(&pkt);
        encode(codex_ctx, outFrame, outpkt, outfile_h264);
        av_packet_unref(&pkt);
    }
    encode(codex_ctx, NULL, outpkt, outfile_h264);
    avformat_close_input(&fmt_ctx);
    fclose(outfile_yuv);
    fclose(outfile_h264);
    av_log(NULL, AV_LOG_DEBUG, "end");

    return 0;
}

4.遇到问题

(1)摄像头录制的原始数据为yuyv422,可以在编码器中直接设置输入格式为yuyv422,也可以先把yuyv422转换为yuv420(为什么要转?练练手)

注意:(1)yuyv422的数据格式:两个y分量和一个u、一个v分量共占4个字节,也就是每个分量占一个字节,但是两个y是共用u和v分量的,所以两个y就有了两个像素,算起来我们就可以说4个字节里面有2个像素,所以可以得到yuyv422的每个像素占2个字节,所以每个yuyv422帧的字节数为:w*h*2

(2)yuv420p的数据格式:四个y分量和一个u、一个v分量共占6个字节,同上,6/4=1.5,所以每个yuv420p像素占的字节数为1.5,得到每个yuv420p帧的字节数为:w*h*1.5

(2)编码出来的视频数据少了几秒???

YUV数据是正常的,但是编码成h264出来数据就少了,为什么呢?我检查了编码器的配置,发现并没有问题,那就是编码的过程中,最后是第二天早上仔细看了一下,原来我编码的代码里面第一行打印了frame的参数,但是最后冲刷编码器的时候,传入的是NULL,导致最后冲刷解码器崩溃了,不过并没有报错,只是停止了编码并卡顿了一下,这是我在windows上的情况,已解决。