FFmpeg本地播放器
2021-08-03 本文已影响0人
lieon
播放器具备的功能
- 同时播放音视频
- 单独播放音频,视频
- 开始,暂停,停止
- 静音,音量控制
- 拖动进度
播放流程
- 解封装
- 初始化音频信息
- 初始化视频信息
- 开启音频子线程程,开始音频拉取任务
- 开启视频解码子线程,在这个子线程中while循环会一直读取videoPackList中的头元素,有就进行解码,解码完成,通知外部进行渲染;list为空,则直接continue
- while读取AVPacket,放入对应的数据缓存中
解封装
- 创建解封装上下文
- 检索流信息 (流相关的音频和视频数据都将会在这个
fmtCtx
中) - 打印流信息到控制台
// 创建解封装上下文
ret = avformat_open_input(&fmtCtx, filename, nullptr, nullptr);
END(avformat_open_input);
// 检索流信息
ret = avformat_find_stream_info(fmtCtx, nullptr);
END(avformat_find_stream_info);
// 打印流信息到控制台
av_dump_format(fmtCtx, 0, filename, 0);
fflush(stderr);
初始化解码器
- 根据type寻找到最合适的流信息
- 获取流
- 为当前流找到合适的解码器
- 初始化解码器上下文
- 从流中拷贝参数到解码器上下文中
- 打开解码器
int VideoPlayer::initDecoder(AVCodecContext **decodecCtx, AVStream**stream, AVMediaType type) {
// 根据type寻找到最合适的流信息
int ret = av_find_best_stream(fmtCtx, type, -1, -1, nullptr, 0);
RET(av_find_best_stream);
// 获取流
int streamIdx = ret;
*stream = fmtCtx->streams[streamIdx];
if (!*stream) {
cout << "stream is empty" << endl;
return -1;
}
// 为当前流找到合适的解码器
AVCodec *decoder = avcodec_find_decoder((*stream)->codecpar->codec_id);
if (!decoder) {
cout << "avcodec_find_decoder is empty" << endl;
return -1;
}
// 初始化解码器上下文
*decodecCtx = avcodec_alloc_context3(decoder);
if (!decodecCtx) {
cout << "avcodec_alloc_context3 error" << endl;
}
// 从流中拷贝参数到解码器上下文中
ret = avcodec_parameters_to_context(*decodecCtx, (*stream)->codecpar);
RET(avcodec_parameters_to_context);
// 打开解码器
ret = avcodec_open2(*decodecCtx, decoder, nullptr);
RET(avcodec_open2);
return 0;
}
初始化音频信息
- 初始化解码器
- 初始化音频重采样
- 初始化SDL
int VideoPlayer::initAudioInfo() {
// 初始化解码器
int ret = initDecoder(&aDecodeCtx, &aStream, AVMEDIA_TYPE_AUDIO);
RET(initDecoder);
// 初始化音频重采样
ret = initSwr();
RET(initSwr);
// 初始化SDL
ret = initSDL();
RET(initSDL);
return 0;
}
初始化音频重采样
- 设置重采样输入参数
- 设置重采样输出参数
- 创建重采样上下文
- 初始化重采样上下文
- 初始化重采样的输入frame
- 初始化重采样的输出frame
- 为aSwrOutFrame的data[0]分配内存空间
int VideoPlayer::initSwr() {
// 设置重采样输入参数
aSwrInSpec.sampleFmt = aDecodeCtx->sample_fmt;
aSwrInSpec.sampleRate = aDecodeCtx->sample_rate;
aSwrInSpec.chLayout = (int)aDecodeCtx->channel_layout;
aSwrInSpec.chs = aDecodeCtx->channels;
// 设置重采样输出参数
aSwrOutSpec.sampleFmt = AV_SAMPLE_FMT_S16;
aSwrOutSpec.sampleRate = 44100;
aSwrOutSpec.chLayout = AV_CH_LAYOUT_STEREO;
aSwrOutSpec.chs = av_get_channel_layout_nb_channels(aSwrOutSpec.chLayout);
aSwrOutSpec.bytesPerSampleFrame = aSwrOutSpec.chs * av_get_bytes_per_sample(aSwrOutSpec.sampleFmt);
// 创建重采样上下文
aSwrCtx = swr_alloc_set_opts(nullptr,
// 输出参数
aSwrOutSpec.chLayout,
aSwrOutSpec.sampleFmt,
aSwrOutSpec.sampleRate,
// 输入参数
aSwrInSpec.chLayout,
aSwrInSpec.sampleFmt,
aSwrInSpec.sampleRate,
0, nullptr);
if (!aSwrCtx) {
cout << "swr_alloc_set_opts error" << endl;
return -1;
}
// 初始化重采样上下文
int ret = swr_init(aSwrCtx);
RET(swr_init);
// 初始化重采样的输入frame
aSwrOutFrame = av_frame_alloc();
if (!aSwrOutFrame) {
cout << "av_frame_alloc error" << endl;
return -1;
}
// 初始化重采样的输出frame
aSwrInFrame = av_frame_alloc();
if (!aSwrInFrame) {
cout << "av_frame_alloc error" << endl;
return -1;
}
// 为aSwrOutFrame的data[0]分配内存空间
ret = av_samples_alloc(aSwrOutFrame->data,
aSwrOutFrame->linesize,
aSwrOutSpec.chs,
4096, aSwrOutSpec.sampleFmt, 1);
RET(av_samples_alloc)
return 0;
}
初始化SDL
- 设置音频播放参数:采样率,采样格式,声道数,音频缓冲区的样本数量
- 传递给回调的参数
- 设置音频回调
- 打开音频设备
int VideoPlayer::initSDL() {
// 音频参数
SDL_AudioSpec spec;
// 采样率
spec.freq = aSwrOutSpec.sampleRate;
// 采样格式
spec.format = AUDIO_S16LSB;
// 声道数
spec.channels = aSwrOutSpec.chs;
// 音频缓冲区的样本数量
spec.samples = 512;
// 传递给回调的参数
spec.userdata = this;
// 回调
spec.callback = sdlAudioCallbackFunc;
// 打开音频设备
if (SDL_OpenAudio(&spec, nullptr)) {
cout << "SDL_OpenAudio error" << endl;
return -1;
}
return 0;
}
SDL回调
void VideoPlayer::sdlAudioCallbackFunc(void *userData, uint8_t *stream, int len) {
VideoPlayer *player = (VideoPlayer*)userData;
player->sdlAudioCallback(stream, len);
}
void VideoPlayer::sdlAudioCallback(uint8_t *stream, int len) {
// 清零(静音)
SDL_memset(stream, 0, len);
// len: SDL音频缓冲区剩余的大小(还未填充的大小)
while (len > 0) {
if (state == Paused) {
break;
}
if (state == Stopped) {
aCanFree = true;
break;
}
// 说明当前PCM的数据已经全部拷贝到SDL的音频缓冲区了
// 需要解码下一个pkt,获取新的PCM数据
if (aSwrOutIdx >= aSwrOutSiize) {
// 新的PCM的大小
aSwrOutSiize = decodeAudio();
// 索引清0
aSwrOutIdx = 0;
// 没有解码出PCM数据,那就静音处理
if (aSwrOutSiize <= 0) {
// 假定PCM的大小
aSwrOutSiize = 1024;
// 给PCM填充0(静音)
memset(aSwrOutFrame->data[0], 0, aSwrOutSiize);
}
}
// 本次需要填充到stream中的PCM的数据大小
int fillLen = aSwrOutSiize - aSwrOutIdx;
fillLen = min(fillLen, len);
// 获取当前音量
int volumn = mute ? 0 : ((this->volumn * 1.0 / Max) * SDL_MIX_MAXVOLUME);
cout << "volumn" << volumn << endl;
// 填充SDL缓冲区
SDL_MixAudio(stream,
aSwrOutFrame->data[0] + aSwrOutIdx,
fillLen, volumn);
// 移动偏移量
len -= fillLen;
stream += fillLen;
aSwrOutIdx += fillLen;
cout << "SDL_MixAudio fillLen:" << fillLen << " aSwrOutIdx: " << aSwrOutIdx << " aSwrOutSiize: " << aSwrOutSiize << " len: " << len << endl;
}
cout << "len <= 0" << endl;
}
音频解码模块
- 取出音频包
- 发送数据到解码器
- 从解码器中获取解码之后的数据到输入frame
- 重采样PCM
int VideoPlayer::decodeAudio() {
// 加锁
aMutex.lock();
if (aPktList.empty()) {
aMutex.unlock();
return 0;
}
AVPacket pkt = aPktList.front();
aPktList.pop_front();
cout << "list cout: " << aPktList.size() << endl;
aMutex.unlock();
// 保存音频时钟
if (pkt.pts != AV_NOPTS_VALUE) {
aTime = av_q2d(aStream->time_base) * pkt.pts;
// 通知外界:播放时间点发生了改变
if (callback.timeChanged) {
callback.timeChanged(userData, this);
}
}
// 如果是视频,不能在这个位置判断(不能提前释放pkt,不然会导致B帧。P帧解码失败,画面直接撕裂)
// 发现音频的时间是早于seektime的,直接丢弃
if (aSeekTime >= 0) {
if (aTime < aSeekTime) {
// 释放pkt
av_packet_unref(&pkt);
return 0;
} else {
aSeekTime = -1;
}
}
// 发送数据到解码器
int ret = avcodec_send_packet(aDecodeCtx, &pkt);
av_packet_unref(&pkt);
RET(avcodec_send_packet);
// 从解码器中获取解码之后的数据到输入frame
ret = avcodec_receive_frame(aDecodeCtx, aSwrInFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
} else RET(avcodec_receive_frame);
// 重采样输出的样本数
int outSamples = (int)av_rescale_rnd(aSwrOutSpec.sampleRate,
aSwrInFrame->nb_samples,
aSwrInSpec.sampleRate, AV_ROUND_UP);
// 由于解码出来的PCM,跟SDL要求的PCM格式可能不一致
// 所以需要需要重采样
ret = swr_convert(aSwrCtx,
aSwrOutFrame->data,
outSamples,
(const uint8_t**)aSwrInFrame->data,
aSwrInFrame->nb_samples);
RET(swr_convert)
return ret * aSwrOutSpec.bytesPerSampleFrame;
}
初始化视频信息
- 初始化解码器
- 初始化像素格式转换
初始化像素格式转换
- 设置输出参数
- 初始化像素格式转换的上下文
- 初始化像素格式转换的输入frame
- 初始化像素格式转换的输出frame
- 给输出缓冲区_vSwsOutFrame的data分配内存空间
int VideoPlayer::initSws() {
int inW = vDecodeCtx->width;
int inH = vDecodeCtx->height;
// 输出参数
vSwsOutSpec.width = inW >> 4 << 4;
vSwsOutSpec.height = inH >> 4 << 4;
vSwsOutSpec.pixFmt = AV_PIX_FMT_RGB24;
vSwsOutSpec.size = av_image_get_buffer_size(vSwsOutSpec.pixFmt, vSwsOutSpec.width, vSwsOutSpec.height, 1);
// 初始化像素格式转换的上下文
vSwsCtx = sws_getContext(inW,
inH,
vDecodeCtx->pix_fmt,
vSwsOutSpec.width,
vSwsOutSpec.height,
vSwsOutSpec.pixFmt,
SWS_BILINEAR, nullptr, nullptr, nullptr);
if (!vSwsCtx) {
return -1;
}
// 初始化像素格式转换的输入frame
vSwsInFrame = av_frame_alloc();
// 初始化像素格式转换的输出frame
vSwsOutframe = av_frame_alloc();
// 给输出缓冲区_vSwsOutFrame的data分配内存空间
int ret = av_image_alloc(vSwsOutframe->data, vSwsOutframe->linesize, vSwsOutSpec.width, vSwsOutSpec.height, vSwsOutSpec.pixFmt, 1);
RET(av_image_alloc);
return 0;
}
视频解码模块
- 发送数据到解码器
- 获取解码后的数据
- 像素格式装换
- 对外回调视频帧
void VideoPlayer::decodeVideo() {
while (true) {
// 如果是暂停,并且没有Seek操作
if (state == Paused && vSeekTime == -1) {
continue;
}
if (state == Stopped) {
vCanFree = true;
break;
}
vMutex.lock();
if (vPktList.empty()) {
vMutex.unlock();
continue;
}
// 取出头部的视频包
AVPacket pkt = vPktList.front();
vPktList.pop_front();
vMutex.unlock();
// 视频时钟
if (pkt.dts != AV_NOPTS_VALUE) {
vTime = av_q2d(vStream->time_base) * pkt.dts;
}
cout << "vTime: " << vTime << " aTime: " << aTime << endl;
// 发送数据到解码器
int ret = avcodec_send_packet(vDecodeCtx, &pkt);
// 释放pkt
av_packet_unref(&pkt);
CONTINUE(avcodec_send_packet);
while (true) {
// 获取解码后的数据
ret = avcodec_receive_frame(vDecodeCtx, vSwsInFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else BREAK(avcodec_receive_frame);
// 一定要在解码成功后,再进行下面的判断
// 发现视频的时间早于seektime的,直接丢弃
if (vSeekTime >= 0) {
if (vTime < vSeekTime) {
continue;
} else {
vSeekTime = -1;
}
}
// 像素格式装换
sws_scale(vSwsCtx,
vSwsInFrame->data,
vSwsInFrame->linesize,
0,
vDecodeCtx->height,
vSwsOutframe->data,
vSwsOutframe->linesize);
if (hasAudio) {// 有音频
// 如果视频包过早被解码出来,那就需要等待对应的音频时钟到达
while (vTime > aTime && state == Playing) {
cout << "如果视频包过早被解码出来,那就需要等待对应的音频时钟到达" << endl;
SDL_Delay(5);
}
} else {
// TODO
}
// 把像素格式转换后的图片数据,拷贝一份出来
uint8_t *data = (uint8_t*)av_malloc(vSwsOutSpec.size);
memcpy(data, vSwsOutframe->data[0], vSwsOutSpec.size);
// 回调给外部进行渲染
cout << "渲染了一帧" << vSwsOutframe->pts << " 剩余包数量:" << vPktList.size() << endl;
if (callback.didDecodeVideoFrame) {
callback.didDecodeVideoFrame(this->userData, this, data, vSwsOutSpec);
} else {
delete data;
data = nullptr;
}
}
}
}
资源释放模块
- 释放fmtCtx
- 释放音频资源
- 释放视频资源
void VideoPlayer::free() {
while (hasAudio && !aCanFree);
while (hasVideo && !vCanFree);
while (!fmtCtxCanFree);
avformat_close_input(&fmtCtx);
fmtCtxCanFree = false;
seekTime = -1;
freeAudio();
freeVideo();
}
void VideoPlayer::freeAudio() {
aTime = 0;
aSwrOutIdx = 0;
aSwrOutSiize = 0;
aStream = nullptr;
aCanFree = false;
aSeekTime = -1;
clearAudioPktList();
avcodec_free_context(&aDecodeCtx);
swr_free(&aSwrCtx);
av_frame_free(&aSwrInFrame);
if (aSwrOutFrame) {
av_freep(&aSwrOutFrame->data[0]);
av_frame_free(&aSwrOutFrame);
}
SDL_PauseAudio(1);
SDL_CloseAudio();
}
void VideoPlayer::freeVideo() {
clearVideoPktList();
avcodec_free_context(&vDecodeCtx);
av_frame_free(&vSwsInFrame);
if (vSwsOutframe) {
av_freep(&vSwsOutframe->data[0]);
av_frame_free(&vSwsOutframe);
}
sws_freeContext(vSwsCtx);
vSwsCtx = nullptr;
vStream = nullptr;
vTime = 0;
vCanFree = false;
vSeekTime = -1;
}
总体流程
void VideoPlayer::readFile() {
int ret = 0;
// 创建解封装上下文
ret = avformat_open_input(&fmtCtx, filename, nullptr, nullptr);
END(avformat_open_input);
// 检索流信息
ret = avformat_find_stream_info(fmtCtx, nullptr);
END(avformat_find_stream_info);
// 打印流信息到控制台
av_dump_format(fmtCtx, 0, filename, 0);
fflush(stderr);
// 初始化音频信息
hasAudio = initAudioInfo() >= 0;
// 初始化视频信息
hasVideo = initVideoInfo() >= 0;
// 到此为止初始化完毕
cout << "初始化完毕" << endl;
setState(Playing);
// 音频解码子线程:开始工作
SDL_PauseAudio(0);
// 视频解码子线程:开始工作
thread([this]() {
decodeVideo();
}).detach();
// 从输入文件中读取数据
AVPacket pkt;
while (state != Stopped) {
// 处理Seek操作
if (seekTime >= 0) {
int streamIdx;
if (hasAudio) { // 优先使用音频流索引
streamIdx = aStream->index;
} else {
streamIdx = vStream->index;
}
AVRational timeBase = fmtCtx->streams[streamIdx]->time_base;
int64_t ts = seekTime / av_q2d(timeBase);
ret = av_seek_frame(fmtCtx, streamIdx, ts, AVSEEK_FLAG_BACKWARD);
if (ret < 0) { // seek失败
seekTime = -1;
cout << "Seek 失败" << seekTime << ts << streamIdx << endl;
} else {
cout << "Seek 成功" << seekTime << ts << streamIdx << endl;
vSeekTime = seekTime;
aSeekTime = seekTime;
seekTime = -1;
aTime = 0;
vTime = 0;
// 清空之前的读取的数据包
clearAudioPktList();
clearVideoPktList();
}
}
int vSize = (int)vPktList.size();
int aSize = (int)aPktList.size();
if (vSize >= AUDIO_MAX_PKT_SIZE || aSize >= AUDIO_MAX_PKT_SIZE) {
continue;
}
ret = av_read_frame(fmtCtx, &pkt);
if (ret == 0) {
if (pkt.stream_index == aStream->index) {
addAudioPkt(pkt);
} else if (pkt.stream_index == vStream->index) {
addVideoPkt(pkt);
} else {
av_packet_unref(&pkt);
}
} else if (ret == AVERROR_EOF) { // 读取到了文件的尾部
if (vSize == 0 && aSize == 0) {
// 说明文件正常播放完毕
fmtCtxCanFree = true;
break;
}
} else {
ERROR_BUF;
continue;
}
}
if (fmtCtxCanFree) {
stop();
} else {
fmtCtxCanFree = true;
}
}
对外接口
void VideoPlayer::setFilename(string name) {
const char *filename = name.c_str();
memcpy(this->filename, filename, strlen(filename) + 1);
}
void VideoPlayer::stop() {
if (state == Stopped) {
return;
}
state = Stopped;
free();
// 通知外界
if (callback.stateChanged) {
callback.stateChanged(userData, this);
}
}
bool VideoPlayer::isPlaying() {
return state == Playing;
}
VideoPlayer::State VideoPlayer::getState() {
return this->state;
}
int VideoPlayer::getDuration() {
return fmtCtx ? fmtCtx->duration * av_q2d(AV_TIME_BASE_Q) : 0;
}
int VideoPlayer::getTime() {
return round(aTime);
}
void VideoPlayer::setTime(double seekTime) {
int duration = getDuration();
this->seekTime = round(seekTime * duration * 1.0 / 1.0);
}
void VideoPlayer::setVolumn(double volumn) {
this->volumn = round(volumn * Max);
}
int VideoPlayer::getVolumn() {
return volumn;
}
void VideoPlayer::setMute(bool mute) {
this->mute = mute;
}
bool VideoPlayer::isMute() {
return this->mute;
}
音视频同步方案
- 采用音频同步视频方案
- 1.获取当前音频的时间
aTime = av_q2d(aStream->time_base) * pkt.pts;
- 2.获取当前视频的时间
vTime = av_q2d(vStream->time_base) * pkt.dts;
- 3.如果视频包的时间大于音频包的时间,那就需要等待对应的音频时钟到达,才进行帧渲染,否则就原地等待
// 如果视频包过早被解码出来,那就需要等待对应的音频时钟到达 while (vTime > aTime && state == Playing) { SDL_Delay(5); }
- 1.获取当前音频的时间
需要明确的一些时间相关的概念
- 现实时间
- 比如一个视频的时长是120秒,其中120秒就是现实时间
- 比如一个视频播放到了第58秒,其中第58秒就是现实时间
- FFmpeg时间
- 时间戳(timestamp),类型是int64_t
- 时间基(time base\unit),是时间戳的单位,类型是AVRational
- FFmpeg时间 与 现实时间的转换
- 现实时间 = 时间戳 * (时间基的分子 / 时间基的分母)
- 现实时间 = 时间戳 * av_q2d(时间基)
- 时间戳 = 现实时间 / (时间基的分子 / 时间基的分母)
- 时间戳 = 现实时间 / av_q2d(时间基)