1. 程式人生 > >Qt實戰--基於FFmpeg的視訊引擎實現類

Qt實戰--基於FFmpeg的視訊引擎實現類

FFmpeg

搞音視訊開發的基本都會接觸到FFmpeg這個庫,支援幾乎所有的音視訊編解碼格式。相對於上節我們用OpenCV實現的獲取視訊幀,FFmpeg對底層的控制粒度更細,有利於我們後續開發,精準控制編解碼格式,獲取碼流資訊,實現進度調整等;

FFmpeg編譯或下載教程網上很多,在此就不介紹了。

在此主要貼出一張解碼流程圖:

FFmpeg解碼流程圖

HFFPlayer

#include "hffplayer.h"
#include "hw/hlog.h"
#include "hw/hstring.h"
#include "hw/hscope.h"

string strtime(int64 us) {
    int sec = us / 1000000;

    int min = sec / 60;
    sec = sec % 60;

    int hour = min / 60;
    min = min % 60;

    return asprintf("%02d:%02d:%02d", hour, min, sec);
}

#define WITH_CUVID

std::atomic_flag HFFPlayer::s_init_flag;

HFFPlayer::HFFPlayer()
: HVideoPlayer()
, HThread() {
    fmt_ctx = NULL;
    codec_ctx = NULL;
    packet = NULL;
    frame = NULL;

    if (!s_init_flag.test_and_set()) {
        av_register_all();
        avcodec_register_all();
        //avdevice_register_all();
    }
}

HFFPlayer::~HFFPlayer() {
    cleanup();
}

int HFFPlayer::start(){
    std::string ifile;

    AVInputFormat* ifmt = NULL;
    switch (media.type) {
    case MEDIA_TYPE_CAPTURE:
        ifile = "video=";
        ifile += media.src;
        ifmt = av_find_input_format("dshow"); 
        if (ifmt == NULL) {
            hloge("Can not find dshow");
            return -5;
        }
        break;
    case MEDIA_TYPE_FILE:
    case MEDIA_TYPE_NETWORK:
        ifile = media.src;
        break;
    default:
        return -10;
    }

    hlogi("ifile:%s", ifile.c_str());
    int iRet = 0;
    fmt_ctx = avformat_alloc_context();
    if (fmt_ctx == NULL) {
        hloge("avformat_alloc_context");
        return -10;
    }
    iRet = avformat_open_input(&fmt_ctx, ifile.c_str(), ifmt, NULL);
    if (iRet != 0) {
        hloge("Open input file[%s] failed: %d", ifile.c_str(), iRet);
        return iRet;
    }

    hlogi("duration:%s", strtime(fmt_ctx->duration).c_str());

    iRet = avformat_find_stream_info(fmt_ctx, NULL);
    if (iRet != 0) {
        hloge("Can not find stream: %d", iRet);
        return iRet;
    }

    hlogi("stream_num=%d", fmt_ctx->nb_streams);

    video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    subtitle_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0);
    hlogi("video_stream_index=%d", video_stream_index);
    hlogi("audio_stream_index=%d", audio_stream_index);
    hlogi("subtitle_stream_index=%d", subtitle_stream_index);

    if (video_stream_index < 0) {
        hloge("Can not find video stream.");
        return -20;
    }

    AVCodecParameters* codecpar = fmt_ctx->streams[video_stream_index]->codecpar;
    hlogi("codec_id=%d:%s", codecpar->codec_id, avcodec_get_name(codecpar->codec_id));

    std::string cuvid(avcodec_get_name(codecpar->codec_id));
    cuvid += "_cuvid";
    hlogi("cuvid=%s", cuvid.c_str());

    AVCodec* codec = NULL;
#ifdef WITH_CUVID
    if (codec == NULL) {
        codec = avcodec_find_decoder_by_name(cuvid.c_str());
        if (codec == NULL) {
            hlogi("Can not find decoder %s!", cuvid.c_str());
            // no return, try soft-decoder
        }
    }
#endif

    if (codec == NULL) {
        codec = avcodec_find_decoder(codecpar->codec_id);
        if (codec == NULL) {
            hloge("Can not find decoder %s!", avcodec_get_name(codecpar->codec_id));
            return -20;
        }
    }

    hlogi("codec_name: %s=>%s", codec->name, codec->long_name);

    codec_ctx = avcodec_alloc_context3(codec);
    if (codec_ctx == NULL) {
        hloge("avcodec_alloc_context3!");
        return -60;
    }

    iRet = avcodec_parameters_to_context(codec_ctx, codecpar);
    if (iRet != 0) {
        hloge("avcodec_parameters_to_context error: %d", iRet);
        return iRet;
    }

    iRet = avcodec_open2(codec_ctx, codec, NULL);
    if (iRet != 0) {
        hloge("Can not open codec: %d", iRet);
        return iRet;
    }

    int sw = codec_ctx->width;
    int sh = codec_ctx->height;
    AVPixelFormat src_pix_fmt = codec_ctx->pix_fmt;

    int dw = sw >> 2 << 2; // align = 4
    int dh = sh;

    hframe.w = dw;
    hframe.h = dh;

    dst_pix_fmt = AV_PIX_FMT_BGR24;
    hframe.type = GL_BGR;
    hframe.bpp = 24;
    hframe.buf.init(dw * dh * hframe.bpp / 8);
    data[0] = hframe.buf.base;
    linesize[0] = dw * 3;

    // dst_pix_fmt = AV_PIX_FMT_YUV420P;
    // hframe.type = GL_I420;
    // hframe.bpp = 12;
    // int y_size = dw * dh;
    // hframe.buf.init(y_size * 3 / 2);
    // data[0] = hframe.buf.base;
    // data[1] = data[0] + y_size;
    // data[2] = data[1] + y_size/4;
    // linesize[0] = dw;
    // linesize[1] = linesize[2] = dw / 2;

    frame = av_frame_alloc();
    packet = av_packet_alloc();
    sws_ctx = sws_getContext(sw, sh, src_pix_fmt, dw, dh, dst_pix_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL);

    hlogi("sw=%d sh=%d dw=%d dh=%d", sw, sh, dw, dh);
    hlogi("src_pix_fmt=%d:%s dst_pix_fmt=%d:%s", src_pix_fmt, av_get_pix_fmt_name(src_pix_fmt), 
        dst_pix_fmt, av_get_pix_fmt_name(dst_pix_fmt));

    HThread::setSleepPolicy(HThread::SLEEP_UNTIL, 1000/fps);
    return HThread::start();
}

void HFFPlayer::cleanup() {
    if (codec_ctx) {
        avcodec_close(codec_ctx);
        avcodec_free_context(&codec_ctx);
        codec_ctx = NULL;
    }

    if (fmt_ctx) {
        avformat_close_input(&fmt_ctx);
    }
    
    if (fmt_ctx) {
        avformat_free_context(fmt_ctx);
        fmt_ctx = NULL;
    }

    if (frame) {
        av_frame_unref(frame);
        av_frame_free(&frame);
        frame = NULL;
    }
    
    if (packet) {
        av_packet_unref(packet);
        av_packet_free(&packet);
        packet = NULL;
    }

    hframe.buf.cleanup();

    if (sws_ctx) {
        sws_freeContext(sws_ctx);
        sws_ctx = NULL;
    }
}

int HFFPlayer::stop(){
    HThread::stop();
    cleanup();
    return 0;
}

void HFFPlayer::doTask(){
    // loop until get a video frame
    while (1) {
        av_init_packet(packet);
        int iRet = av_read_frame(fmt_ctx, packet);
        if (iRet != 0) {
            hlogi("No frame: %d", iRet);
            return;
        }

        if (packet->stream_index != video_stream_index) {
            continue;
        }

        int got_pic = 0;
        iRet = avcodec_decode_video2(codec_ctx, frame, &got_pic, packet);
        if (iRet < 0) {
            hloge("decoder error: %d", iRet);
            return;
        }

        if (got_pic)    break;  // exit loop
    }

    sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, data, linesize);

    push_frame(&hframe);
}