/**
|
* 叶海辉
|
* QQ群121376426
|
* http://blog.yundiantech.com/
|
*/
|
|
#include "VideoPlayerZZCX.h"
|
|
VideoPlayerZZCX::VideoPlayerZZCX()
|
{
|
initPlayer();
|
}
|
|
VideoPlayerZZCX::~VideoPlayerZZCX()
|
{
|
|
}
|
|
void VideoPlayerZZCX::readVideoFile()
|
{
|
///SDL初始化需要放入子线程中,否则有些电脑会有问题。
|
if (SDL_Init(SDL_INIT_AUDIO))
|
{
|
doOpenSdlFailed(-100);
|
fprintf(stderr, "Could not initialize SDL - %s. \n", SDL_GetError());
|
return;
|
}
|
|
mIsReadThreadFinished = false;
|
mIsReadFinished = false;
|
|
const char * file_path = mFilePath.c_str();
|
|
pFormatCtx = nullptr;
|
pCodecCtx = nullptr;
|
pCodec = nullptr;
|
|
aCodecCtx = nullptr;
|
aCodec = nullptr;
|
aFrame = nullptr;
|
|
mAudioStream = nullptr;
|
mVideoStream = nullptr;
|
|
audio_clock = 0;
|
video_clock = 0;
|
|
int audioStream ,videoStream;
|
|
//Allocate an AVFormatContext.
|
pFormatCtx = avformat_alloc_context();
|
|
AVDictionary* opts = NULL;
|
av_dict_set(&opts, "rtsp_transport", "tcp", 0); //设置tcp or udp,默认一般优先tcp再尝试udp
|
av_dict_set(&opts, "stimeout", "60000000", 0);//设置超时3秒
|
|
if (avformat_open_input(&pFormatCtx, file_path, nullptr, &opts) != 0)
|
{
|
fprintf(stderr, "can't open the file. \n");
|
doOpenVideoFileFailed();
|
goto end;
|
}
|
|
if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
|
{
|
fprintf(stderr, "Could't find stream infomation.\n");
|
doOpenVideoFileFailed();
|
goto end;
|
}
|
|
videoStream = -1;
|
audioStream = -1;
|
|
///循环查找视频中包含的流信息,
|
for (int i = 0; i < pFormatCtx->nb_streams; i++)
|
{
|
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
|
{
|
videoStream = i;
|
}
|
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audioStream < 0)
|
{
|
audioStream = i;
|
}
|
}
|
|
doTotalTimeChanged(getTotalTime());
|
|
///打开视频解码器,并启动视频线程
|
if (videoStream >= 0)
|
{
|
///查找视频解码器
|
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
|
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
|
|
if (pCodec == nullptr)
|
{
|
fprintf(stderr, "PCodec not found.\n");
|
doOpenVideoFileFailed();
|
goto end;
|
}
|
// pCodecCtx->time_base.den = 14;
|
// pCodecCtx->time_base.num = 1;
|
|
///打开视频解码器
|
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
|
{
|
fprintf(stderr, "Could not open video codec.\n");
|
doOpenVideoFileFailed();
|
goto end;
|
}
|
|
mVideoStream = pFormatCtx->streams[videoStream];
|
|
///创建一个线程专门用来解码视频
|
std::thread([&](VideoPlayerZZCX *pointer)
|
{
|
pointer->decodeVideoThread();
|
|
}, this).detach();
|
|
}
|
|
if (audioStream >= 0)
|
{
|
///查找音频解码器
|
// aCodecCtx = pFormatCtx->streams[audioStream]->codec;
|
// printf("aCodecCtx->codec_id:%d\n", aCodecCtx->codec_id);
|
// aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
|
int codecId = AV_CODEC_ID_AAC;
|
aCodec = avcodec_find_decoder((AVCodecID)codecId);
|
if (!aCodec) {
|
fprintf(stderr, "Codec not found\n");
|
return;
|
}
|
|
aCodecCtx = avcodec_alloc_context3(aCodec);
|
if (!aCodecCtx) {
|
fprintf(stderr, "Could not allocate audio codec context\n");
|
return;
|
}
|
|
aCodecCtx->codec = aCodec;
|
aCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;
|
aCodecCtx->bit_rate = 36000;
|
aCodecCtx->sample_rate = 16000;
|
aCodecCtx->channels = 1;
|
aCodecCtx->channel_layout = AV_CH_LAYOUT_MONO;
|
aCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
|
|
if (aCodec == NULL)
|
{
|
fprintf(stderr, "ACodec not found.\n");
|
audioStream = -1;
|
}
|
else
|
{
|
///打开音频解码器
|
if (avcodec_open2(aCodecCtx, aCodec, nullptr) < 0)
|
{
|
fprintf(stderr, "Could not open audio codec.\n");
|
doOpenVideoFileFailed();
|
goto end;
|
}
|
|
///解码音频相关
|
aFrame = av_frame_alloc();
|
|
|
//重采样设置选项-----------------------------------------------------------start
|
aFrame_ReSample = nullptr;
|
|
//frame->16bit 44100 PCM 统一音频采样格式与采样率
|
swrCtx = nullptr;
|
|
//输入的声道布局
|
int in_ch_layout;
|
|
//输出的声道布局
|
int out_ch_layout = av_get_default_channel_layout(audio_tgt_channels); ///AV_CH_LAYOUT_STEREO
|
|
out_ch_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;
|
|
/// 这里音频播放使用了固定的参数
|
/// 强制将音频重采样成44100 双声道 AV_SAMPLE_FMT_S16
|
/// SDL播放中也是用了同样的播放参数
|
//重采样设置选项----------------
|
//输入的采样格式
|
in_sample_fmt = aCodecCtx->sample_fmt;
|
//输出的采样格式 16bit PCM
|
out_sample_fmt = AV_SAMPLE_FMT_S16;
|
//输入的采样率
|
in_sample_rate = aCodecCtx->sample_rate;
|
//输入的声道布局
|
in_ch_layout = aCodecCtx->channel_layout;
|
|
//输出的采样率
|
out_sample_rate = 44100;
|
//输出的声道布局
|
|
audio_tgt_channels = 2; ///av_get_channel_layout_nb_channels(out_ch_layout);
|
out_ch_layout = av_get_default_channel_layout(audio_tgt_channels); ///AV_CH_LAYOUT_STEREO
|
|
out_ch_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;
|
|
/// 2019-5-13添加
|
/// wav/wmv 文件获取到的aCodecCtx->channel_layout为0会导致后面的初始化失败,因此这里需要加个判断。
|
if (in_ch_layout <= 0)
|
{
|
in_ch_layout = av_get_default_channel_layout(aCodecCtx->channels);
|
}
|
|
swrCtx = swr_alloc_set_opts(nullptr, out_ch_layout, out_sample_fmt, out_sample_rate,
|
in_ch_layout, in_sample_fmt, in_sample_rate, 0, nullptr);
|
|
/** Open the resampler with the specified parameters. */
|
int ret = swr_init(swrCtx);
|
if (ret < 0)
|
{
|
char buff[128]={0};
|
av_strerror(ret, buff, 128);
|
|
fprintf(stderr, "Could not open resample context %s\n", buff);
|
swr_free(&swrCtx);
|
swrCtx = nullptr;
|
doOpenVideoFileFailed();
|
goto end;
|
}
|
|
//存储pcm数据
|
int out_linesize = out_sample_rate * audio_tgt_channels;
|
|
// out_linesize = av_samples_get_buffer_size(NULL, audio_tgt_channels, av_get_bytes_per_sample(out_sample_fmt), out_sample_fmt, 1);
|
out_linesize = AVCODEC_MAX_AUDIO_FRAME_SIZE;
|
|
|
mAudioStream = pFormatCtx->streams[audioStream];
|
|
///打开SDL播放声音
|
int code = openSDL();
|
|
if (code == 0)
|
{
|
SDL_LockAudioDevice(mAudioID);
|
SDL_PauseAudioDevice(mAudioID,0);
|
SDL_UnlockAudioDevice(mAudioID);
|
|
mIsAudioThreadFinished = false;
|
}
|
else
|
{
|
doOpenSdlFailed(code);
|
}
|
}
|
|
}
|
|
// av_dump_format(pFormatCtx, 0, file_path, 0); //输出视频信息
|
|
mPlayerState = VideoPlayer_Playing;
|
doPlayerStateChanged(VideoPlayer_Playing, mVideoStream != nullptr, mAudioStream != nullptr);
|
|
mVideoStartTime = av_gettime();
|
fprintf(stderr, "%s mIsQuit=%d mIsPause=%d \n", __FUNCTION__, mIsQuit, mIsPause);
|
while (1)
|
{
|
if (mIsQuit)
|
{
|
//停止播放了
|
break;
|
}
|
|
if (seek_req)
|
{
|
int stream_index = -1;
|
int64_t seek_target = seek_pos;
|
|
if (videoStream >= 0)
|
stream_index = videoStream;
|
else if (audioStream >= 0)
|
stream_index = audioStream;
|
|
AVRational aVRational = {1, AV_TIME_BASE};
|
if (stream_index >= 0)
|
{
|
seek_target = av_rescale_q(seek_target, aVRational, pFormatCtx->streams[stream_index]->time_base);
|
}
|
|
if (av_seek_frame(pFormatCtx, stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0)
|
{
|
fprintf(stderr, "%s: error while seeking\n",pFormatCtx->filename);
|
}
|
else
|
{
|
if (audioStream >= 0)
|
{
|
AVPacket packet;
|
av_new_packet(&packet, 10);
|
strcpy((char*)packet.data,FLUSH_DATA);
|
clearAudioQuene(); //清除队列
|
inputAudioQuene(packet); //往队列中存入用来清除的包
|
}
|
|
if (videoStream >= 0)
|
{
|
AVPacket packet;
|
av_new_packet(&packet, 10);
|
strcpy((char*)packet.data,FLUSH_DATA);
|
clearVideoQuene(); //清除队列
|
inputVideoQuene(packet); //往队列中存入用来清除的包
|
video_clock = 0;
|
}
|
|
mVideoStartTime = av_gettime() - seek_pos;
|
mPauseStartTime = av_gettime();
|
}
|
seek_req = 0;
|
seek_time = seek_pos / 1000000.0;
|
seek_flag_audio = 1;
|
seek_flag_video = 1;
|
|
if (mIsPause)
|
{
|
mIsNeedPause = true;
|
mIsPause = false;
|
}
|
|
}
|
|
//这里做了个限制 当队列里面的数据超过某个大小的时候 就暂停读取 防止一下子就把视频读完了,导致的空间分配不足
|
//这个值可以稍微写大一些
|
if (mAudioPacktList.size() > MAX_AUDIO_SIZE || mVideoPacktList.size() > MAX_VIDEO_SIZE)
|
{
|
mSleep(10);
|
continue;
|
}
|
|
if (mIsPause == true)
|
{
|
mSleep(10);
|
continue;
|
}
|
|
AVPacket packet;
|
if (av_read_frame(pFormatCtx, &packet) < 0)
|
{
|
mIsReadFinished = true;
|
|
if (mIsQuit)
|
{
|
break; //解码线程也执行完了 可以退出了
|
}
|
|
mSleep(10);
|
continue;
|
}
|
|
if (packet.stream_index == videoStream)
|
{
|
inputVideoQuene(packet);
|
//这里我们将数据存入队列 因此不调用 av_free_packet 释放
|
}
|
else if( packet.stream_index == audioStream )
|
{
|
if (mIsAudioThreadFinished)
|
{ ///SDL没有打开,则音频数据直接释放
|
av_packet_unref(&packet);
|
}
|
else
|
{
|
inputAudioQuene(packet);
|
//这里我们将数据存入队列 因此不调用 av_free_packet 释放
|
}
|
|
}
|
else
|
{
|
// Free the packet that was allocated by av_read_frame
|
av_packet_unref(&packet);
|
}
|
}
|
|
///文件读取结束 跳出循环的情况
|
///等待播放完毕
|
while (!mIsQuit)
|
{
|
mSleep(100);
|
}
|
|
end:
|
|
clearAudioQuene();
|
clearVideoQuene();
|
|
if (mPlayerState != VideoPlayer_Stop) //不是外部调用的stop 是正常播放结束
|
{
|
stop();
|
}
|
|
while((mVideoStream != nullptr && !mIsVideoThreadFinished) || (mAudioStream != nullptr && !mIsAudioThreadFinished))
|
{
|
mSleep(10);
|
} //确保视频线程结束后 再销毁队列
|
|
closeSDL();
|
|
if (swrCtx != nullptr)
|
{
|
swr_free(&swrCtx);
|
swrCtx = nullptr;
|
}
|
|
if (aFrame != nullptr)
|
{
|
av_frame_free(&aFrame);
|
aFrame = nullptr;
|
}
|
|
if (aFrame_ReSample != nullptr)
|
{
|
av_frame_free(&aFrame_ReSample);
|
aFrame_ReSample = nullptr;
|
}
|
|
if (aCodecCtx != nullptr)
|
{
|
avcodec_close(aCodecCtx);
|
aCodecCtx = nullptr;
|
}
|
|
if (pCodecCtx != nullptr)
|
{
|
avcodec_close(pCodecCtx);
|
pCodecCtx = nullptr;
|
}
|
|
avformat_close_input(&pFormatCtx);
|
avformat_free_context(pFormatCtx);
|
|
SDL_Quit();
|
|
doPlayerStateChanged(VideoPlayer_Stop, mVideoStream != nullptr, mAudioStream != nullptr);
|
|
mIsReadThreadFinished = true;
|
|
fprintf(stderr, "%s finished \n", __FUNCTION__);
|
}
|