发布于 2019-01-24   490人围观  0条评论
发表于 2019-01-24   490人围观  0条评论

前言

本文参考雷霄骅的博文最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器,使用c++根据ffmpeg-4.1版本改写(原文代码基于旧版本ffmpeg)。代码见下文。

本文代码地址见https://github.com/2997215859/ffplay-learn/blob/master/Video/decode2yuv.cpp

本文代码基于ffmpeg-4.1版本,事先需要安装好ffmpeg

本文代码提供CMakeLists.txt,见附录CMakeLists.txt部分,或根据CMakeLists.txt改写。需要链接的库如下(基本上安装ffmpeg、ffplay、SDL2之后就有了)。

avdevice avfilter avformat avcodec swscale swresample postproc avutil m xcb xcb-shm xcb xcb-shape xcb xcb-xfixes xcb-render xcb-shape xcb asound pthread m fontconfig freetype freetype z bz2 lzma SDL2 SDL2main

代码注解

#include <iostream>
#include <cstdio>
#include <cstdlib>

#ifdef __cplusplus
extern "C" {
#endif

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include <libavdevice/avdevice.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>

#ifdef __cplusplus
};
#endif

using namespace std;

int main () {

    string filepath = "/home/sensetime/videos/big_buck_bunny_720p_30mb.mp4";
    string outputFile = "/home/sensetime/videos/output.yuv";

    // initialization,初始化
    avdevice_register_all();
    avformat_network_init();

    AVFormatContext *avFormatContext = avformat_alloc_context();
    if (avformat_open_input(&avFormatContext, filepath.c_str(), NULL, NULL) != 0) { // 打开文件,生成format协议上下文
        cerr << "Failed to open input stream.\n";
        return -1;
    }
    if (avformat_find_stream_info(avFormatContext, NULL) < 0) { // 查看文件格式信息
        cerr << "Failed to find stream information\n";
        return -1;
    }

    // 定位第一个视频流(video stream)
    int videoIndex = -1;
    for (int i=0;i<avFormatContext->nb_streams;i++) {
        if(avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            videoIndex = i;
            break;
        }
    }

    if (videoIndex == -1) {
        cerr << "Failed to find a video stream.\n";
        return -1;
    }

    AVStream *avStream = avFormatContext->streams[videoIndex]; // 获取该流指针
    AVCodec *avCodec = avcodec_find_decoder(avStream->codecpar->codec_id); // 获取该流的解码器
    if (!avCodec) {
        cerr << "Failed to find decoder for stream.\n";
        return -1;
    }

    AVCodecContext *avCodecContext = avcodec_alloc_context3(avCodec); // 为该解码器新建解码上下文
    if (!avCodecContext) {
        cerr << "Failed to allocate the decoder context for stream\n";
        return -1;
    }
    if(avcodec_parameters_to_context(avCodecContext, avStream->codecpar) < 0) { // 将流的解码参数拷贝到解码上下文
        cerr << "Failed to copy decoder parameters to input decoder context.\n";
        return -1;
    }

    if (avcodec_open2(avCodecContext, avCodec, NULL) < 0) { // 打开解码器
        cerr << "Failed to open codec.\n";
        return -1;
    }

    AVFrame *yuvFrame = av_frame_alloc();

    // 根据输入的解码上下文获取输入流的宽高,基于宽高和图片每个像素的存储方式,分配每个图片的存储空间
    unsigned char* outBuffer = (unsigned char*) av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, avCodecContext->width, avCodecContext->height, 1));
    av_image_fill_arrays(yuvFrame->data, yuvFrame->linesize, outBuffer, AV_PIX_FMT_YUV420P, avCodecContext->width, avCodecContext->height, 1);


    // Output Info ----
    cout << "--------------- File Information ---------------\n";
    av_dump_format(avFormatContext, 0, filepath.c_str(), 0);
    cout << "-----------------------------------------------\n";

    // 创建图像转换上下文
    SwsContext *imgConvertContext = sws_getContext(avCodecContext->width, avCodecContext->height, avCodecContext->pix_fmt, avCodecContext->width, avCodecContext->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

    AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    AVFrame *frame = av_frame_alloc();

    FILE *fpYUV = fopen(outputFile.c_str(), "wb+");
    while (av_read_frame(avFormatContext, packet) >= 0) { // 从format上下文读取压缩编码的packet
        if (packet->stream_index == videoIndex) {
            if (avcodec_send_packet(avCodecContext, packet) < 0) { // 将packet提交到解码上下文,进行解码
                cerr << "Failed to submitting the packet to the decoder\n";
                return -1;
            }

            // 从解码上下文中读取所有输出的frames
            int ret;
            while ((ret = avcodec_receive_frame(avCodecContext, frame)) >= 0) {
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF || ret < 0) {
                    cerr << "Error during decoding\n";
                    return -1;
                }
                
                // sws_scaled的作用是将frame作像素转化和图像拉伸等等,这里是将数据转换到新的yuvFrame
                sws_scale(imgConvertContext, frame->data, frame->linesize, 0, avCodecContext->height, yuvFrame->data, yuvFrame->linesize);
                int ySize = avCodecContext->width * avCodecContext->height;
                // 根据YUV420的特点,将每帧的数据以此按照Y、U、V三层保存到输出文件中
                fwrite(yuvFrame->data[0], 1, ySize, fpYUV); // Y
                fwrite(yuvFrame->data[1], 1, ySize/4, fpYUV); // U
                fwrite(yuvFrame->data[2], 1, ySize/4, fpYUV); // V
                cerr << "Succeed to decode 1 frame!\n";
            }

        }
        av_packet_unref(packet);
    }

    sws_freeContext(imgConvertContext);
    fclose(fpYUV);
    av_frame_free(&yuvFrame);
    av_frame_free(&frame);
    avcodec_close(avCodecContext);
    avformat_close_input(&avFormatContext);

    return 0;
}

附录

CMakeLists.txt

cmake_minimum_required(VERSION 3.13)
project(player)

set(CMAKE_CXX_STANDARD 11)

include_directories(.)
include_directories(/usr/include/.)
include_directories(/usr/local/include/.)

#link_directories(/usr/lib/)
link_directories(/usr/local/lib/)

# 设置可执行文件生成路径
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin")


# 生成debug版本
SET(CMAKE_BUILD_TYPE "release")
if (CMAKE_BUILD_TYPE STREQUAL debug)
    add_definitions(-D_DEBUG)
endif ()
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb -std=c++11")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall -std=c++11")


add_executable(decode2yuv decode2yuv.cpp)
target_link_libraries(decode2yuv
        avdevice
        avfilter
        avformat
        avcodec
        swscale
        swresample
        postproc
        avutil
        #        avresample
        m xcb xcb-shm xcb xcb-shape xcb xcb-xfixes xcb-render xcb-shape xcb asound
        pthread m fontconfig freetype freetype
        z
        bz2
        lzma
        SDL2
        SDL2main)


 

 

 

上一篇: C++中使用ffmpeg播放YUV数据

下一篇: C++版本的基于FFMPEG的Helloworld程序

立即登录,发表评论
没有帐号?立即注册
0 条评论