1背景
之前的文章从打开文件开始,都是qt本身的界面相关。一直没看到有关的ffmpeg播放的。从这篇开始关注播放。
2步骤
2.1 HThread::start
上一篇 ffmpeg播放器5 里提到 start调用了HThread::start,这里使用了一个库libhv 中的hthread.h 其中start方法如下
virtual int start() {
if (status == STOP) {
thread = std::thread([this] {
if (!doPrepare()) return;
setStatus(RUNNING);
run();
setStatus(STOP);
if (!doFinish()) return;
});
}
return 0;
}
virtual bool doPrepare() {return true;}
2.2 doPrepare
从上面的代码可以看到start会先调用doPrepare
在面向对象编程中,当子类继承父类并重写其方法后,父类的方法在执行时,若调用了被子类覆盖的同名方法,则会调用子类的版本。这是由多态性(Polymorphism)决定的:方法调用基于对象的实际类型(子类实例),而非引用类型(父类方法定义)。
之前start实际对象是hffplayer,继续从该文件找doPrepare的实现
bool HFFPlayer::doPrepare() {
int ret = open();
if (ret != 0) {
if (!quit) {
error = ret;
event_callback(HPLAYER_OPEN_FAILED);
}
return false;
}
else {
event_callback(HPLAYER_OPENED);
}
return true;
}
这里调用了open方法,直接在hffplayer中查看定义
int HFFPlayer::open() {
std::string ifile;
AVInputFormat* ifmt = NULL;
switch (media.type) {
case MEDIA_TYPE_CAPTURE:
{
ifile = "video=";
ifile += media.src;
#ifdef _WIN32
const char drive[] = "dshow";
#elif defined(__linux__)
const char drive[] = "v4l2";
#else
const char drive[] = "avfoundation";
#endif
ifmt = av_find_input_format(drive);
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 ret = 0;
fmt_ctx = avformat_alloc_context();
if (fmt_ctx == NULL) {
hloge("avformat_alloc_context");
ret = -10;
return ret;
}
defer (if (ret != 0 && fmt_ctx) {avformat_free_context(fmt_ctx); fmt_ctx = NULL;})
if (media.type == MEDIA_TYPE_NETWORK) {
if (strncmp(media.src.c_str(), "rtsp:", 5) == 0) {
std::string str = g_confile->GetValue("rtsp_transport", "video");
if (strcmp(str.c_str(), "tcp") == 0 ||
strcmp(str.c_str(), "udp") == 0) {
av_dict_set(&fmt_opts, "rtsp_transport", str.c_str(), 0);
}
}
av_dict_set(&fmt_opts, "stimeout", "5000000", 0); // us
}
av_dict_set(&fmt_opts, "buffer_size", "2048000", 0);
fmt_ctx->interrupt_callback.callback = interrupt_callback;
fmt_ctx->interrupt_callback.opaque = this;
block_starttime = time(NULL);
ret = avformat_open_input(&fmt_ctx, ifile.c_str(), ifmt, &fmt_opts);
if (ret != 0) {
hloge("Open input file[%s] failed: %d", ifile.c_str(), ret);
return ret;
}
fmt_ctx->interrupt_callback.callback = NULL;
defer (if (ret != 0 && fmt_ctx) {avformat_close_input(&fmt_ctx);})
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret != 0) {
hloge("Can not find stream: %d", ret);
return ret;
}
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.");
ret = -20;
return ret;
}
AVStream* video_stream = fmt_ctx->streams[video_stream_index];
video_time_base_num = video_stream->time_base.num;
video_time_base_den = video_stream->time_base.den;
hlogi("video_stream time_base=%d/%d", video_stream->time_base.num, video_stream->time_base.den);
AVCodecParameters* codec_param = video_stream->codecpar;
hlogi("codec_id=%d:%s", codec_param->codec_id, avcodec_get_name(codec_param->codec_id));
AVCodec* codec = NULL;
if (decode_mode != SOFTWARE_DECODE) {
try_hardware_decode:
std::string decoder(avcodec_get_name(codec_param->codec_id));
if (decode_mode == HARDWARE_DECODE_CUVID) {
decoder += "_cuvid";
real_decode_mode = HARDWARE_DECODE_CUVID;
}
else if (decode_mode == HARDWARE_DECODE_QSV) {
decoder += "_qsv";
real_decode_mode = HARDWARE_DECODE_QSV;
}
codec = avcodec_find_decoder_by_name(decoder.c_str());
if (codec == NULL) {
hlogi("Can not find decoder %s", decoder.c_str());
// goto try_software_decode;
}
hlogi("decoder=%s", decoder.c_str());
}
if (codec == NULL) {
try_software_decode:
codec = avcodec_find_decoder(codec_param->codec_id);
if (codec == NULL) {
hloge("Can not find decoder %s", avcodec_get_name(codec_param->codec_id));
ret = -30;
return ret;
}
real_decode_mode = SOFTWARE_DECODE;
}
hlogi("codec_name: %s=>%s", codec->name, codec->long_name);
codec_ctx = avcodec_alloc_context3(codec);
if (codec_ctx == NULL) {
hloge("avcodec_alloc_context3");
ret = -40;
return ret;
}
defer (if (ret != 0 && codec_ctx) {avcodec_free_context(&codec_ctx); codec_ctx = NULL;})
ret = avcodec_parameters_to_context(codec_ctx, codec_param);
if (ret != 0) {
hloge("avcodec_parameters_to_context error: %d", ret);
return ret;
}
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
av_dict_set(&codec_opts, "refcounted_frames", "1", 0);
}
ret = avcodec_open2(codec_ctx, codec, &codec_opts);
if (ret != 0) {
if (real_decode_mode != SOFTWARE_DECODE) {
hlogi("Can not open hardwrae codec error: %d, try software codec.", ret);
goto try_software_decode;
}
hloge("Can not open software codec error: %d", ret);
return ret;
}
video_stream->discard = AVDISCARD_DEFAULT;
int sw, sh, dw, dh;
sw = codec_ctx->width;
sh = codec_ctx->height;
src_pix_fmt = codec_ctx->pix_fmt;
hlogi("sw=%d sh=%d src_pix_fmt=%d:%s", sw, sh, src_pix_fmt, av_get_pix_fmt_name(src_pix_fmt));
if (sw <= 0 || sh <= 0 || src_pix_fmt == AV_PIX_FMT_NONE) {
hloge("Codec parameters wrong!");
ret = -45;
return ret;
}
dw = sw >> 2 << 2; // align = 4
dh = sh;
dst_pix_fmt = AV_PIX_FMT_YUV420P;
std::string str = g_confile->GetValue("dst_pix_fmt", "video");
if (!str.empty()) {
if (strcmp(str.c_str(), "YUV") == 0) {
dst_pix_fmt = AV_PIX_FMT_YUV420P;
}
else if (strcmp(str.c_str(), "RGB") == 0) {
dst_pix_fmt = AV_PIX_FMT_BGR24;
}
}
hlogi("dw=%d dh=%d dst_pix_fmt=%d:%s", dw, dh, dst_pix_fmt, av_get_pix_fmt_name(dst_pix_fmt));
sws_ctx = sws_getContext(sw, sh, src_pix_fmt, dw, dh, dst_pix_fmt, SWS_BICUBIC, NULL, NULL, NULL);
if (sws_ctx == NULL) {
hloge("sws_getContext");
ret = -50;
return ret;
}
packet = av_packet_alloc();
frame = av_frame_alloc();
hframe.w = dw;
hframe.h = dh;
// ARGB
hframe.buf.resize(dw * dh * 4);
if (dst_pix_fmt == AV_PIX_FMT_YUV420P) {
hframe.type = PIX_FMT_IYUV;
hframe.bpp = 12;
int y_size = dw * dh;
hframe.buf.len = y_size * 3 / 2;
data[0] = (uint8_t*)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;
}
else {
dst_pix_fmt = AV_PIX_FMT_BGR24;
hframe.type = PIX_FMT_BGR;
hframe.bpp = 24;
hframe.buf.len = dw * dh * 3;
data[0] = (uint8_t*)hframe.buf.base;
linesize[0] = dw * 3;
}
// HVideoPlayer member vars
if (video_stream->avg_frame_rate.num && video_stream->avg_frame_rate.den) {
fps = video_stream->avg_frame_rate.num / video_stream->avg_frame_rate.den;
}
width = sw;
height = sh;
duration = 0;
start_time = 0;
eof = 0;
error = 0;
if (video_time_base_num && video_time_base_den) {
if (video_stream->duration > 0) {
duration = video_stream->duration / (double)video_time_base_den * video_time_base_num * 1000;
}
if (video_stream->start_time > 0) {
start_time = video_stream->start_time / (double)video_time_base_den * video_time_base_num * 1000;
}
}
hlogi("fps=%d duration=%lldms start_time=%lldms", fps, duration, start_time);
HThread::setSleepPolicy(HThread::SLEEP_UNTIL, 1000 / fps);
return ret;
}
int HFFPlayer::close() {
if (fmt_opts) {
av_dict_free(&fmt_opts);
fmt_opts = NULL;
}
if (codec_opts) {
av_dict_free(&codec_opts);
codec_opts = NULL;
}
if (codec_ctx) {
avcodec_close(codec_ctx);
avcodec_free_context(&codec_ctx);
codec_ctx = NULL;
}
if (fmt_ctx) {
avformat_close_input(&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;
}
if (sws_ctx) {
sws_freeContext(sws_ctx);
sws_ctx = NULL;
}
hframe.buf.cleanup();
return 0;
}
这里的方法太长了,简单列下要实现的功能
主要用于使用 FFmpeg 库初始化媒体播放环境,打开媒体源(如摄像头、本地文件、网络流),并配置解码器及像素格式转换,为后续解码和渲染做准备。以下是其核心作用的详细分析:
2.3 ffmpeg 头文件
在src/util/ffmpeg_util.h中有如下代码
extern "C" {
#include "libavutil/avutil.h"
#include "libavutil/pixdesc.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#include "libswscale/swscale.h"
}
FFmpeg提供的不同库的用法。
- libavcodec编码/解码库
- libavfilter基于图的框架编辑库
- libavformatI/O和muxing/demuxing库
- libavdevice专用设备muxing/demuxing库
- libavutil公用实用程序库
- libswresample音频重采样、格式转换和混音
- libswscale颜色转换和缩放库
2.4 打开媒体文件
ret = avformat_open_input(&fmt_ctx, ifile.c_str(), ifmt, &fmt_opts);
if (ret != 0) {
hloge("Open input file[%s] failed: %d", ifile.c_str(), ret);
return ret;
}
2.5 查找是否有流
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret != 0) {
hloge("Can not find stream: %d", ret);
return ret;
}
2.6 获取视频流音频流索引
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.");
ret = -20;
return ret;
}
2.7 根据索引获取视频流
AVStream* video_stream = fmt_ctx->streams[video_stream_index];
video_time_base_num = video_stream->time_base.num;
video_time_base_den = video_stream->time_base.den;
hlogi("video_stream time_base=%d/%d", video_stream->time_base.num, video_stream->time_base.den);