Github
https://github.com/gongluck/FFmpeg4.0-study/tree/master/Cff
CDecode.h
代码语言:javascript复制#ifndef __CDECODE_H__
#define __CDECODE_H__
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#ifdef __cplusplus
}
#endif
#include <string>
#include <mutex>
#include <thread>
class CDecode
{
public:
~CDecode();
// 状态
enum STATUS{STOP, DECODING};
// 帧类型
enum FRAMETYPE {ERR, VIDEO, AUDIO};
// 状态通知回调声明
typedef void (*DecStatusCallback)(STATUS status, std::string err, void* param);
// 解码帧回调声明
typedef void (*DecFrameCallback)(const AVFrame* frame, FRAMETYPE frametype, void* param);
// 设置输入
bool set_input(const std::string& input, std::string& err);
// 获取输入
const std::string& get_input(std::string& err);
// 设置解码帧回调
bool set_dec_callback(DecFrameCallback cb, void* param, std::string& err);
// 设置解码状态变化回调
bool set_dec_status_callback(DecStatusCallback cb, void* param, std::string& err);
// 开始解码
bool begindecode(std::string& err);
// 停止解码
bool stopdecode(std::string& err);
private:
// 解码线程
bool decodethread();
private:
STATUS status_ = STOP;
std::recursive_mutex mutex_;
std::string input_;
int vindex_ = -1;
int aindex_ = -1;
std::thread decodeth_;
DecStatusCallback decstatuscb_ = nullptr;
void* decstatuscbparam_ = nullptr;
DecFrameCallback decframecb_ = nullptr;
void* decframecbparam_ = nullptr;
//ffmpeg
AVFormatContext* fmtctx_ = nullptr;
AVCodecContext* vcodectx_ = nullptr;
AVCodecContext* acodectx_ = nullptr;
};
#endif __CDECODE_H__
CDecode.cpp
代码语言:javascript复制#include "CDecode.h"
// C 中使用av_err2str宏
char av_error[AV_ERROR_MAX_STRING_SIZE] = { 0 };
#define av_err2str(errnum)
av_make_error_string(av_error, AV_ERROR_MAX_STRING_SIZE, errnum)
// 递归锁
#define LOCK() std::lock_guard<std::recursive_mutex> _lock(this->mutex_)
// 检查停止状态
#define CHECKSTOP(err)
if(this->status_ != STOP)
{
err = "status is not stop.";
return false;
}
// 检查ffmpeg返回值
#define CHECKFFRET(ret)
if (ret < 0)
{
err = av_err2str(ret);
return false;
}
#define CHECKFFRETANDCTX(ret, codectx)
if (ret < 0)
{
avcodec_free_context(&codectx);
err = av_err2str(ret);
return false;
}
#define CHECKFFRETANDCTX2(ret, codectx1, codectx2)
if (ret < 0)
{
avcodec_free_context(&codectx1);
avcodec_free_context(&codectx2);
err = av_err2str(ret);
return false;
}
CDecode::~CDecode()
{
std::string err;
stopdecode(err);
}
bool CDecode::set_input(const std::string& input, std::string& err)
{
LOCK();
CHECKSTOP(err);
if (input.empty())
{
err = "input is empty.";
return false;
}
else
{
input_ = input;
err = "opt succeed.";
return true;
}
}
const std::string& CDecode::get_input(std::string& err)
{
LOCK();
err = "opt succeed.";
return input_;
}
bool CDecode::set_dec_callback(DecFrameCallback cb, void* param, std::string& err)
{
LOCK();
CHECKSTOP(err);
decframecb_ = cb;
decframecbparam_ = param;
err = "opt succeed.";
return true;
}
bool CDecode::set_dec_status_callback(DecStatusCallback cb, void* param, std::string& err)
{
LOCK();
CHECKSTOP(err);
decstatuscb_ = cb;
decstatuscbparam_ = param;
err = "opt succeed.";
return true;
}
bool CDecode::begindecode(std::string& err)
{
LOCK();
CHECKSTOP(err);
int ret;
if (!stopdecode(err))
{
return false;
}
fmtctx_ = avformat_alloc_context();
if (fmtctx_ == nullptr)
{
err = "avformat_alloc_context() return nullptr.";
return false;
}
ret = avformat_open_input(&fmtctx_, input_.c_str(), nullptr, nullptr);
CHECKFFRET(ret);
ret = avformat_find_stream_info(fmtctx_, nullptr);
CHECKFFRET(ret);
// 查找流
AVCodec* vcodec = nullptr;
ret = av_find_best_stream(fmtctx_, AVMEDIA_TYPE_VIDEO, -1, -1, &vcodec, 0);
vindex_ = ret;
AVCodec* acodec = nullptr;
ret = av_find_best_stream(fmtctx_, AVMEDIA_TYPE_AUDIO, -1, -1, &acodec, 0);
aindex_ = ret;
if (vindex_ < 0 && aindex_ < 0)
{
err = "cant find stream.";
return false;
}
if (vindex_ >= 0)
{
vcodectx_ = avcodec_alloc_context3(vcodec);
if (vcodectx_ == nullptr)
{
err = "avcodec_alloc_context3(vcodec) return nullptr.";
return false;
}
ret = avcodec_parameters_to_context(vcodectx_, fmtctx_->streams[vindex_]->codecpar);
CHECKFFRETANDCTX(ret, vcodectx_);
ret = avcodec_open2(vcodectx_, vcodec, nullptr);
CHECKFFRETANDCTX(ret, vcodectx_);
}
if (aindex_ >= 0)
{
acodectx_ = avcodec_alloc_context3(acodec);
if (acodectx_ == nullptr)
{
err = "avcodec_alloc_context3(acodec) return nullptr.";
return false;
}
ret = avcodec_parameters_to_context(acodectx_, fmtctx_->streams[aindex_]->codecpar);
CHECKFFRETANDCTX2(ret, vcodectx_, acodectx_);
ret = avcodec_open2(acodectx_, acodec, nullptr);
CHECKFFRETANDCTX2(ret, vcodectx_, acodectx_);
}
av_dump_format(fmtctx_, 0, input_.c_str(), 0);
status_ = DECODING;
std::thread th(&CDecode::decodethread, this);
decodeth_.swap(th);
return true;
}
bool CDecode::stopdecode(std::string& err)
{
LOCK();
status_ = STOP;
if (decodeth_.joinable())
{
decodeth_.join();
}
if (vcodectx_ != nullptr)
{
avcodec_free_context(&vcodectx_);
}
if (acodectx_ != nullptr)
{
avcodec_free_context(&acodectx_);
}
avformat_close_input(&fmtctx_);
vindex_ = aindex_ = -1;
err = "opt succeed.";
return true;
}
bool CDecode::decodethread()
{
int ret;
std::string err;
AVPacket* packet = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
if (packet == nullptr || frame == nullptr)
{
if (decstatuscb_ != nullptr)
{
status_ = STOP;
decstatuscb_(STOP, "av_packet_alloc() or av_frame_alloc() return nullptr.", decstatuscbparam_);
}
av_packet_free(&packet);
av_frame_free(&frame);
return false;
}
av_init_packet(packet);
while (true)
{
if (status_ != DECODING)
break;
if (av_read_frame(fmtctx_, packet) < 0)
{
if (decstatuscb_ != nullptr)
{
status_ = STOP;
decstatuscb_(STOP, "end of file.", decstatuscbparam_);
}
break; //这里认为视频读取完了
}
if (packet->stream_index == vindex_)
{
// 解码视频帧
ret = avcodec_send_packet(vcodectx_, packet);
if (ret < 0)
{
if (decstatuscb_ != nullptr)
{
status_ = STOP;
decstatuscb_(STOP, av_err2str(ret), decstatuscbparam_);
}
break;
}
while (ret >= 0)
{
ret = avcodec_receive_frame(vcodectx_, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret < 0)
{
if (decstatuscb_ != nullptr)
{
decstatuscb_(DECODING, av_err2str(ret), decstatuscbparam_);
}
break;
}
else
{
// 得到解码数据
if (decframecb_ != nullptr)
{
decframecb_(frame, VIDEO, decframecbparam_);
}
}
}
}
else if (packet->stream_index == aindex_)
{
// 解码音频帧
ret = avcodec_send_packet(acodectx_, packet);
if (ret < 0)
{
if (decstatuscb_ != nullptr)
{
status_ = STOP;
decstatuscb_(STOP, av_err2str(ret), decstatuscbparam_);
}
break;
}
while (ret >= 0)
{
ret = avcodec_receive_frame(acodectx_, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret < 0)
{
if (decstatuscb_ != nullptr)
{
decstatuscb_(DECODING, av_err2str(ret), decstatuscbparam_);
}
break;
}
else
{
// 得到解码数据
if (decframecb_ != nullptr)
{
decframecb_(frame, AUDIO, decframecbparam_);
}
}
}
}
av_packet_unref(packet);
}
av_packet_free(&packet);
av_frame_free(&frame);
return true;
}
测试
代码语言:javascript复制#include "CDecode.h"
#include <iostream>
#include <fstream>
void DecStatusCB(CDecode::STATUS status, std::string err, void* param)
{
std::cout << std::this_thread::get_id() << " got a status " << status << std::endl;
}
void DecFrameCB(const AVFrame* frame, CDecode::FRAMETYPE frametype, void* param)
{
//std::cout << std::this_thread::get_id() << " got a frame." << frametype << std::endl;
if (frametype == CDecode::FRAMETYPE::VIDEO)
{
if (frame->format == AV_PIX_FMT_YUV420P)
{
static std::ofstream video("out.yuv", std::ios::binary | std::ios::trunc);
static int i = 0;
if ( i > 10)
return;
video.write(reinterpret_cast<const char*>(frame->data[0]), frame->linesize[0] * frame->height);
video.write(reinterpret_cast<const char*>(frame->data[1]), frame->linesize[1] * frame->height / 2);
video.write(reinterpret_cast<const char*>(frame->data[2]), frame->linesize[2] * frame->height / 2);
}
}
}
int main(int argc, char* argv[])
{
std::string err;
bool ret = false;
CDecode decode;
ret = decode.set_input("in.flv", err);
ret = decode.set_dec_callback(DecFrameCB, nullptr, err);
ret = decode.set_dec_status_callback(DecStatusCB, nullptr, err);
int i = 0;
while (i < 10)
{
ret = decode.begindecode(err);
std::cout << "input to stop decoding." << std::endl;
getchar();
ret = decode.stopdecode(err);
}
return 0;
}