FFmpeg学习

2021-06-09  本文已影响0人  一枚懒人

FFmpeg总结

    FFmpeg 开源音视频库,主要用来处理视频数据等,可以编码,解码,缩放,音频重采样等功能

1:mp4文件抽取H264文件

2:H264文件抽取YUV文件

抽取出yuv数据主要包含3步

1:首先将mp4文件中的H264码流文件抽取出来

2:将h264文件送进去解码

3:得到解码后的每一帧数据,进行保存

上述3步的步骤代码详细如下:

首先是将对后续要使用的ffmpeg API 和对象初始化

// 初始化基本参数,主要包括
// avpacket 解码前数据
// avCodec AVCodec指针对象
// avCodecParserContext AVCodecParserContext指针对象,主要
// avCodecContext AVCodecContext指针对象 编码器codec的上下文
// avFrame AVFrame 指针对象 存放解码后数据
avPacket = av_packet_alloc();
    if (!avPacket) {
        LOGE("AVPACKET open failed");
        goto end;
    }
    memset(input + INBUF_SIZE_NEW, 0, AV_INPUT_BUFFER_PADDING_SIZE);
    avCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!avCodec) {
        LOGE("avcodec_find_decoder  open failed");
        goto end;
    }
    LOGE("avcodec_find_decoder  open sucess");
    avCodecParserContext = av_parser_init(avCodec->id);
    if (!avCodecParserContext) {
        LOGE("avCodecParserContext  init failed");
        goto end;
    }
    LOGE("av_parser_init  open sucess");
    avCodecContext = avcodec_alloc_context3(avCodec);
//    av_find_best_stream(avFormatContext,AVMEDIA_TYPE_VIDEO,)

    if (!avCodecContext) {
        LOGE("avcodec_alloc_context3 failed");
        goto end;
    }
    LOGE("avcodec_alloc_context3  open sucess");
    result = avcodec_open2(avCodecContext, avCodec, nullptr);
    if (result < 0) {
        LOGE("avcodec_open2 failed");
        goto end;
    }
    LOGE("avcodec_open2  open sucess");
    file = fopen(src_file, "rbe");
    if (!file) {
        LOGE("open file failed, error is %s:", strerror(errno));
    }

    LOGE("fopen  open sucess");
    avFrame = av_frame_alloc();
    if (!avFrame) {
        LOGE("avframe alloc failed");
        goto end;
    }

第二步将文件读取的数据进行读取倒avpacket中

while (!feof(file)) {
    //每次从文件中读取INBUF_SIZE_NEW + AV_INPUT_BUFFER_PADDING_SIZE = 4096 +64 个大小的数据
    data_size = fread(input, 1, INBUF_SIZE_NEW, file);
    if (!data_size) {
        break;
    }
    LOGE("fread  sucess :%d ", data_size);
    data = input;
    while (data_size > 0) {
        //将4096 +64  个数据中读取到result个数据到avPacket 
        result = av_parser_parse2(avCodecParserContext, avCodecContext, &avPacket->data,
                                  &avPacket->size, data, data_size, AV_NOPTS_VALUE,
                                  AV_NOPTS_VALUE, 0);
        LOGE("av_parser_parse2  open sucess");
        if (result < 0) {
            LOGE("av_parser_parse2 failed");
            goto end;;
        }
        //指针后移
        data += result;
        data_size -= result;
        //数据送去解码,并保存成yuv文件
        if (avPacket->size) {
            decode(avCodecContext, avFrame, avPacket, des_file);
        }
        LOGE("decode   sucess");
    }
}
decode(avCodecContext, avFrame, nullptr, des_file);

将avpacket数据送到解码器中解码

static void decode(AVCodecContext *avCodecContext, AVFrame *avFrame, AVPacket *pkt,
                   const char *filename) {
    char buf[1024];
    int ret;

    //将packet发送给codec
    ret = avcodec_send_packet(avCodecContext, pkt);
    if (ret < 0) {
        __android_log_print(ANDROID_LOG_ERROR, TAG_ZHF, "Error sending a packet for decoding: %s\n",
                            av_err2str(ret));
        return;
    }

    while (ret >= 0) {
        //解码出frame并存入avFrame参数
        ret = avcodec_receive_frame(avCodecContext, avFrame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            return;
        } else if (ret < 0) {
            __android_log_write(ANDROID_LOG_ERROR, TAG_ZHF, "Error during decoding\n");
            return;
        }

        //为防止文件太多观察不便,每20个avFrame中抽取一个并保存为文件
        if (avCodecContext->frame_number % 20 == 0) {
            __android_log_print(ANDROID_LOG_ERROR, TAG_ZHF, "saving avFrame %3d\n",
                                avCodecContext->frame_number);

            /* the picture is allocated by the decoder. no need to
               free it */
            //拼接文件名
            //C库函数:int snprintf(char *str, size_t size, const char *format, ...),将可变参数(...)按照format格式化成字符串,
            //并将字符串复制到str中,size为要写入的字符的最大数目,超过size会被截断。
            snprintf(buf, sizeof(buf), "%s-%d.yuv", filename, avCodecContext->frame_number);
            yuv_save(avFrame, buf);
        }
    }
}

将数据保存成yuv文件

tatic void yuv_save(AVFrame *avFrame, char *filename) {
    FILE *file;

    file = fopen(filename, "we");
    if (!file) {
        __android_log_write(ANDROID_LOG_ERROR, TAG_ZHF, "Could not open out file\n");
        __android_log_write(ANDROID_LOG_ERROR, TAG_ZHF, strerror(errno));
        return;
    }
    int width = avFrame->width;
    int height = avFrame->height;
    for (int i = 0; i < height; i++)
        fwrite(avFrame->data[0] + i * avFrame->linesize[0], 1, width, file);
    for (int j = 0; j < height / 2; j++)
        fwrite(avFrame->data[1] + j * avFrame->linesize[1], 1, width / 2, file);
    for (int k = 0; k < height / 2; k++)
        fwrite(avFrame->data[2] + k * avFrame->linesize[2], 1, width / 2, file);

    fclose(file);
}
yuv文件展示.png

详细代码参考github:

代码传送门

上一篇下一篇

猜你喜欢

热点阅读