오디오 및 비디오 개발 15: 멀티미디어 오디오 데이터를 PCM으로 디코딩

논리적 흐름

비디오 디코딩의 일반적인 단계는 다음과 같습니다.

  1. 출력 멀티미디어 파일 컨텍스트 가져오기
  2. 멀티미디어 파일에서 비디오 스트림 찾기
  3. 코덱 찾기
  4. 디코더 컨텍스트 만들기
  5. 디코더 매개변수 설정
  6. 개방형 코덱
  7. AVFrame 만들기
  8. AV패킷 생성
  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