音视频开发十五:解码多媒体音频数据为PCM

逻辑流程

视频解码一般步骤为:

  1. 获取输出多媒体文件上下文
  2. 从多多媒体文件找找到视频流
  3. 查找解码器
  4. 创建解码器上下文
  5. 设置解码器参数
  6. 打开解码器
  7. 创建AVFrame
  8. 创建AVPacket
  9. 从多媒体文件读数据
  10. 将压缩数据包送入解码器
  11. 得到解码后的数据帧

具体逻辑流程图如下:

代码实现

读取mp4文件的视频流 ,解码为原始视频帧,对每一个视频帧保存为图片

#include <stdio.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>


// 将视频帧数据保存为图片
static void savePic(unsigned char* buf, int linesize, int width, int height, char* name) {
    
    
    FILE* f;

    f = fopen(name, "wb");
    // 向文件中写入一个P5格式的图像头信息,
    // 该图像的宽度为width像素,高度为height像素,每个像素的最大灰度值为255。
    fprintf(f, "P5\n%d %d\n%d\n", width, height, 255);
    /*
    * 使用for循环遍历每一行的像素数据,
    其中buf是图像数据的首地址,linesize是每行数据的字节数。通过将buf的位置向后偏移i*linesize,获取当前行的像素数据,
    然后使用fwrite函数将该行像素数据写入文件中。
    fwrite函数中,第一个参数是要写入的数据的首地址,第二个参数是每个数据的大小,这里为1,第三个参数是要写入的数据数量,这里为图像的宽度。
    因此,每次循环写入一个完整的行的像素数据。
    */
    for (int i = 0; i < height; i++) {
    
    
        fwrite(buf + i * linesize, 1, width, f);
    }
    fclose(f);
}

// 解码上下文,解码后存放的数据,解码数据包,输出文件名
static int decode(AVCodecContext* ctx, AVFrame* frame, AVPacket* pkt, const char* fileName) {
    
    
    
    int ret = -1;
    char buf[1024];

    ret = avcodec_send_packet(ctx, pkt);
    if (ret < 0) {
    
    
        av_log(NULL, AV_LOG_ERROR, "Failed to send frame to decoder!\n");
        goto _END;
    }

    while (ret >= 0) {
    
    
        ret = avcodec_receive_frame(ctx, frame); // 从编解码器的输出队列中获取解码后的输出帧
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
    
    
            return 0;
        }
        else if (ret < 0) {
    
    
            return -1; //退出程序
        }

        // 没有问题的话,把frame中的内容保存成一张张图片
        //snprintf(buf, sizeof(buf), "%s-%d.bmp", fileName, ctx->frame_number);// 通过这个方法为每一张图片构造一个名字
        snprintf(buf, sizeof(buf), "%s-%d", fileName, ctx->frame_number);// 通过这个方法为每一张图片构造一个名字
        //saveBMP(swsCtx, frame, 640, 360, buf);


        savePic(frame->data[0],
            frame->linesize[0],
            frame->width,
            frame->height,
            buf);

        if (pkt) {
    
    
            av_packet_unref(pkt);
        }

    }
_END:
    return 0;
}

// 对多媒体文件中的视频流进行解码,生成一张张黑白图像
int decode_video() {
    
    
    int ret = -1;
    int idx = -1;

    //1. 处理一些参数;
    char* src;
    char* dst;

    const AVCodec* codec = NULL;
    AVCodecContext* ctx = NULL;

    AVFormatContext* pFmtCtx = NULL;

    AVStream* inStream = NULL;

    AVFrame* frame = NULL;
    AVPacket* pkt = NULL;

    struct SwsContext* swsCtx = NULL;

    av_log_set_level(AV_LOG_DEBUG);


    src = "F:\\test_data\\crop_jiuzhe_summer.mp4";
    dst = "F:\\test_data\\photo\\out";

    //2. 打开多媒体文件
    if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL)) < 0) {
    
    
        av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));
        exit(-1);
    }

    //3. 从多媒体文件中找到视频流
    idx = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (idx < 0) {
    
    
        av_log(pFmtCtx, AV_LOG_ERROR, "Does not include audio stream!\n");
        goto _ERROR;
    }

    inStream = pFmtCtx->streams[idx];

    //4. 查找解码器 找到源文件的编码器id
    codec = avcodec_find_decoder(inStream->codecpar->codec_id);
    if (!codec) {
    
    
        av_log(NULL, AV_LOG_ERROR, "Could not find libx264 Codec");
        goto _ERROR;
    }

    //5. 创建解码器上下文
    ctx = avcodec_alloc_context3(NULL);
    if (!ctx) {
    
    
        av_log(NULL, AV_LOG_ERROR, "NO MEMRORY\n");
        goto _ERROR;
    }

    // 解码器的一些参数信息 可以通过源文件的来获取
    ret = avcodec_parameters_to_context(ctx, inStream->codecpar);
    if (ret < 0) {
    
    
        av_log(ctx, AV_LOG_ERROR, "Could not copyt codecpar to codec ctx!\n");
        goto _ERROR;
    }

    //5. 解码器与解码器上下文绑定到一起
    ret = avcodec_open2(ctx, codec, NULL);
    if (ret < 0) {
    
    
        av_log(ctx, AV_LOG_ERROR, "Don't open codec: %s \n", av_err2str(ret));
        goto _ERROR;
    }


    //6. 创建AVFrame
    frame = av_frame_alloc();
    if (!frame) {
    
    
        av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
        goto _ERROR;
    }

    //7. 创建AVPacket
    pkt = av_packet_alloc();
    if (!pkt) {
    
    
        av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
        goto _ERROR;
    }

    //8. 从源多媒体文件中读到视频数据
    while (av_read_frame(pFmtCtx, pkt) >= 0) {
    
    
        if (pkt->stream_index == idx) {
    
    
            decode(ctx, frame, pkt, dst);
        }
    }
    // 对于解码器和编码器存在同样的问题,有些数据可能还没有解出来,强制让它输出
    decode(ctx, frame, NULL, dst);

    //9. 将申请的资源释放掉
_ERROR:
    if (pFmtCtx) {
    
    
        avformat_close_input(&pFmtCtx);
        pFmtCtx = NULL;
    }

    if (ctx) {
    
    
        avcodec_free_context(&ctx);
        ctx = NULL;
    }

    if (frame) {
    
    
        av_frame_free(&frame);
        frame = NULL;
    }

    if (pkt) {
    
    
        av_packet_free(&pkt);
        pkt = NULL;
    }


    printf("hello, world!\n");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_38056514/article/details/130190827