一、简介
本播放器是在vs2019 x86下开发,通过ffmpeg实现拉流解码功能,通过D3D实现视频的渲染功能。截图功能采用libjpeg实现,可以截取jpg图片,图片的默认保存路径是在C:\MYRecPath中。录像功能采用封装好的类Mp4Record实现,在Mp4Record类中主要还是采用ffmpeg的相关函数方法进行mp4视频的录制。音频的播放采用DirectSound实现,将ffmpeg解码后的音频数据存储到DirectSound的buffer中,再调用DirectSound的play实现对音频的播放功能。码流信息的显示,通过D3D的文本绘制实现。本播放器提供源码下载,直接下载请点击最后的下载链接。
二、界面展示
三、相关代码
开始播放
int CVideoPlayer::StartPlay(const char* sUrl)
{
m_bStop = false;
m_nVideoIndex = -1;
m_nAudioIndex = -1;
int i = 0, res = 0, nValue = 0;
char buf[64] = { 0 };
m_bPlaying = false;
AVStream* in_stream;
_snprintf_s(m_sConnectUrl, sizeof(m_sConnectUrl), sizeof(m_sConnectUrl) - 1, "%s", sUrl);
//av_register_all();
avformat_network_init();
AVDictionary* optionsDict = nullptr;
av_dict_set(&optionsDict, "rtsp_transport", "tcp", 0);
av_dict_set(&optionsDict, "stimeout", "5000000", 0);
av_dict_set(&optionsDict, "buffer_size", "8192000", 0);
av_dict_set(&optionsDict, "recv_buffer_size", "8192000", 0);
m_lastReadPacktTime = av_gettime();
m_pAVFmtCxt = avformat_alloc_context();
m_pAVFmtCxt->interrupt_callback.opaque = this;
m_pAVFmtCxt->interrupt_callback.callback = decodeInterruptCb;
res = avformat_open_input(&m_pAVFmtCxt, m_sConnectUrl, NULL, &optionsDict);
if (res < 0)
{
myprint("avformat_open_input fail: %d", res);
return -1;
}
if (!m_pAVFmtCxt)
{
return -1;
}
m_pAVFmtCxt->probesize = 100 * 1024;
m_pAVFmtCxt->max_analyze_duration = 1 * AV_TIME_BASE;
res = avformat_find_stream_info(m_pAVFmtCxt, NULL);
if (res < 0)
{
myprint("error %x in avformat_find_stream_info\n", res);
return -1;
}
av_dump_format(m_pAVFmtCxt, 0, m_sConnectUrl, 0);
for (i = 0; i < m_pAVFmtCxt->nb_streams; i++)
{
if (m_pAVFmtCxt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
m_nVideoIndex = i;
}
else if (m_pAVFmtCxt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
m_nAudioIndex = i;
}
myprint("m_pAVFmtCxt->streams[i]->codec->codec_type:%d", m_pAVFmtCxt->streams[i]->codecpar->codec_type);
}
//videoindex not find
if (m_nVideoIndex == -1)
{
myprint("can't find video stream.");
return -1;
}
m_pCodec = avcodec_find_decoder(m_pAVFmtCxt->streams[m_nVideoIndex]->codecpar->codec_id);
if (!m_pCodec)
{
myprint("video decoder not found\n");
return -1;
}
if (m_bSupportAudio && m_nAudioIndex != -1)
{
myprint("start audio decoder \n");
if (!m_pAudioCodecCxt)
m_pAudioCodecCxt = avcodec_alloc_context3(NULL);
auto pAudioCodecpar = m_pAVFmtCxt->streams[m_nAudioIndex]->codecpar;
avcodec_parameters_to_context(m_pAudioCodecCxt, pAudioCodecpar);
m_pAudioCodec = avcodec_find_decoder(pAudioCodecpar->codec_id);
if (nullptr == m_pAudioCodec || nullptr == m_pAudioCodecCxt)
{
myprint("audio decoder not found\n");
return -1;
}
m_pSwrContext = swr_alloc_set_opts(0,
av_get_default_channel_layout(m_channels_play),
AV_SAMPLE_FMT_S16,
m_nSampleRate,
av_get_default_channel_layout(m_pAudioCodecCxt->channels),
m_pAudioCodecCxt->sample_fmt,
m_pAudioCodecCxt->sample_rate,
0,
0);
auto ret = swr_init(m_pSwrContext);
if (ret < 0)
{
myprint("Failed to swr_init(pSwrContext);");
return -1;
}
if (InitDirectSound() == FALSE)
m_bSupportAudio = false;
}
m_CodecId = m_pCodec->id;
if(!m_pCodecCxt)
m_pCodecCxt = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(m_pCodecCxt, m_pAVFmtCxt->streams[m_nVideoIndex]->codecpar);
if (m_pCodecCxt)
{
if (m_pCodecCxt->width == 0 || m_pCodecCxt->height == 0)
{
myprint("m_pCodecCxt->width:%d, m_pCodecCxt->height:%d", m_pCodecCxt->width, m_pCodecCxt->height);
return -1;
}
}
else
return -1;
AVCodecContext* temp_codecctx = m_pCodecCxt;
memcpy(temp_codecctx, m_pCodecCxt, sizeof(m_pCodecCxt));
if (m_pCodecCxt->codec_type == AVMEDIA_TYPE_VIDEO)
{
myprint("Soft Solution");
avcodec_close(m_pCodecCxt);
m_pCodecCxt = temp_codecctx;
m_pCodecCxt->thread_count = 4;
if (m_Dxva2D3DRender.InitD3DRender(m_showWnd, m_pCodecCxt->width, m_pCodecCxt->height) == false)
{
myprint("InitD3DRender fail");
}
m_pOutBuffer = (uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, m_pCodecCxt->width, m_pCodecCxt->height, 1));
if (nullptr == m_pOutBuffer)
return -1;
av_image_fill_arrays(m_pFrameBGR->data, m_pFrameBGR->linesize, m_pOutBuffer, AV_PIX_FMT_YUV420P, m_pCodecCxt->width, m_pCodecCxt->height, 1); //填充AVFrame数据缓冲
m_pImgConvertCtx = sws_getContext(m_pCodecCxt->width, m_pCodecCxt->height, m_pCodecCxt->pix_fmt, m_pCodecCxt->width, m_pCodecCxt->height, AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);
if (nullptr == m_pImgConvertCtx)
return -1;
m_nActualWidth = m_pCodecCxt->width;
m_nActualHeight = m_pCodecCxt->height;
res = avcodec_open2(m_pCodecCxt, m_pCodec, NULL);
if (res < 0)
{
myprint("avcodec_open2 video fail error:%x", res);
return -1;
}
}
if (m_bSupportAudio && m_pAudioCodecCxt)
{
if (m_pAudioCodecCxt->codec_type == AVMEDIA_TYPE_AUDIO)
{
res = avcodec_open2(m_pAudioCodecCxt, m_pAudioCodec, NULL);
if (res < 0)
{
myprint("avcodec_open2 audio fail error:%x", res);
avcodec_close(m_pAudioCodecCxt);
return -1;
}
myprint("===Audio Message===");
myprint(" bit_rate = %d ", m_pAudioCodecCxt->bit_rate);
myprint(" sample_rate = %d ", m_pAudioCodecCxt->sample_rate);
myprint(" channels = %d ", m_pAudioCodecCxt->channels);
myprint(" code_name = %s ", m_pAudioCodecCxt->codec->name);
myprint(" block_align = %d ", m_pAudioCodecCxt->block_align);
}
}
m_pDecodeThread = (my_thread_t*)my_malloc(sizeof(my_thread_t));
if (nullptr != m_pDecodeThread)
{
m_bDecodeThreadRun = true;
res = my_thread_create(m_pDecodeThread, ThreadDecode, this);
if (res == -1)
{
myprint("my_thread_create ThreadDecode failed res:%x", res);
return -1;
}
}
return 0;
}
停止播放
void CVideoPlayer::StopPlay()
{
m_bPlaying = false;
m_CaptureAudio.SetGrabAudioFrames(FALSE, NULL);
m_CaptureAudio.Close();
m_bStopAudio = true;
while (!m_AudioPlayQue.empty())
{
m_AudioPlayQue.pop();
}
while (!m_AudioTalkQue.empty())
{
EnterCriticalSection(&m_talklock);
m_AudioTalkQue.pop();
LeaveCriticalSection(&m_talklock);
}
m_bDecodeThreadRun = false;
if (m_pDecodeThread)
{
my_thread_join(m_pDecodeThread);
my_free(m_pDecodeThread);
m_pDecodeThread = nullptr;
}
if (m_pAudioPlayThread) {
SetEvent(m_event);
if (m_pAudioPlayThread->handle) {
my_thread_join(m_pAudioPlayThread);
m_pAudioPlayThread->handle = nullptr;
}
}
if (m_pDSBuffer8)
{
m_pDSBuffer8->Stop();
m_pDSBuffer8->Restore();
}
m_Dxva2D3DRender.UnitD3DRender();
if (m_pCodecCxt)
{
avcodec_close(m_pCodecCxt);
}
if (m_pAudioCodecCxt)
{
avcodec_close(m_pAudioCodecCxt);
}
if (m_pSwrContext)
{
swr_free(&m_pSwrContext);
m_pSwrContext = nullptr;
}
if (m_pAVFmtCxt) {
avformat_close_input(&m_pAVFmtCxt);
avformat_free_context(m_pAVFmtCxt);
m_pAVFmtCxt = nullptr;
}
}
解码线程
void CVideoPlayer::DecodeAndShow()
{
if (m_pAVFmtCxt == nullptr || m_pCodecCxt == nullptr)
return;
AVPacket pkt = { 0 };
m_bPlaying = true;
uint8_t* pBuffer;
bool bEnoughSpace = true;
int nTimeCnt = 0;
int res = 0;
struct SwsContext* img_convert_ctx = nullptr;
int nRectDrawWait = 0;
bool bRecordLastIFrame = false;
int num_av_read_frame_err = 0;
int num_stream_index_err = 0;
uint8_t * outData[2] = {0};
outData[0] = (uint8_t*)av_malloc(1152 * 8);
outData[1] = (uint8_t*)av_malloc(1152 * 8);
uint8_t* pktdata;
int pktsize;
int len = 0;
bool bPushAudioToQueue = false;
m_bStopAudio = false;
CRect ShowRect;
AVFrame* pAvFrame = av_frame_alloc();
if (nullptr == pAvFrame)
return;
AVFrame* pFrameRGB = av_frame_alloc();
if (nullptr == pFrameRGB)
return;
int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, m_pCodecCxt->width, m_pCodecCxt->height, 1);
pBuffer = (uint8_t*)av_malloc(numBytes);
if (nullptr == pBuffer)
return;
av_image_fill_arrays(pFrameRGB->data,pFrameRGB->linesize, pBuffer, AV_PIX_FMT_RGB24, m_pCodecCxt->width, m_pCodecCxt->height, 1);
img_convert_ctx = sws_getCachedContext(img_convert_ctx, m_pCodecCxt->width, m_pCodecCxt->height,
m_pCodecCxt->pix_fmt, m_pCodecCxt->width, m_pCodecCxt->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR/*SWS_BICUBIC*/, NULL, NULL, NULL);
//Audio
if (m_bSupportAudio && m_pAudioPlayThread)
{
res = my_thread_create(m_pAudioPlayThread, ThreadAudioPlay, this);
if (res < 0)
myprint("my_thread_create ThreadAudioPlay fail");
}
while (m_bDecodeThreadRun && !m_bQuit)
{
if (m_bPause)
{
Sleep(100);
continue;
}
{
if (m_bReconnect)
{
myprint("bReConnect = true, break");
break;
}
}
m_lastReadPacktTime = av_gettime();
if (av_read_frame(m_pAVFmtCxt, &pkt) >= 0)
{
num_av_read_frame_err = 0;
if (pkt.stream_index == m_nVideoIndex || pkt.stream_index == m_nAudioIndex)
{
if (m_bRecord)//Record
{
if (nTimeCnt != 0 || (!bRecordLastIFrame && pkt.flags == AV_PKT_FLAG_KEY)) {
if (m_sRecPath[0] != '\0' && nTimeCnt++ % 200 == 0)
{
bEnoughSpace = CheckRemainSpace(m_sRecPath);
if (bEnoughSpace == true)
{
myprint("bEnoughSpace = true");
}
else
myprint("bEnoughSpace = false");
}
m_nRecordCurrentTime = time(NULL);
if ((m_nRecordCurrentTime - m_nRecordStartTime) >= (m_nRecordTime * 60))
{
myprint("Record Finsh!");
stopRecord();
}
else if (bEnoughSpace == false)
{
myprint("bEnoughSpace == false");
stopRecord();
}
else
{
AVPacket* pPkt = av_packet_clone(&pkt);
m_mp4Recorder.saveOneFrame(*pPkt, m_CodecId);
av_packet_free(&pPkt);
}
}
bRecordLastIFrame = pkt.flags == AV_PKT_FLAG_KEY;
}
else
nTimeCnt = 0;
}
if (pkt.stream_index == m_nVideoIndex)
{
num_stream_index_err = 0;
nTimeCnt = 0;
if (pkt.flags == 1)
bPushAudioToQueue = true;
if (nullptr == m_pCodecCxt || nullptr == pAvFrame) {
myprint("m_pCodecCxt == NULL || pAvFrame == NULL");
break;
}
int gotvframe = 0;
auto sd_ret = avcodec_send_packet(m_pCodecCxt, &pkt);
if (sd_ret != 0 && sd_ret != AVERROR(EAGAIN)) {
myprint("avcodec_send_packet err, rt=%d", sd_ret);
enableReConnect();
}
else {
while (gotvframe == 0 && !m_bQuit) {
gotvframe = avcodec_receive_frame(m_pCodecCxt, pAvFrame);
if (gotvframe == 0)
{
try
{
GetShowRectSize(&ShowRect);
}
catch (const std::exception&)
{
myprint("GetClientRect throw, error");
break;
}
m_nCurPKSize = pkt.size;
SetStreamInfoToD3d();
if (pAvFrame->width != m_nActualWidth || pAvFrame->height != m_nActualHeight)
{
myprint("video size change reconnect...");
enableReConnect();
m_nActualWidth = pAvFrame->width;
m_nActualHeight = pAvFrame->height;
av_packet_unref(&pkt);
continue;
}
if (m_pImgConvertCtx && m_pFrameBGR && m_pOutBuffer && pAvFrame)
{
sws_scale(m_pImgConvertCtx, (const uint8_t* const*)pAvFrame->data, pAvFrame->linesize, 0,
m_pCodecCxt->height, m_pFrameBGR->data, m_pFrameBGR->linesize);
{
try
{
int re = 5;
for (int i = 0; m_bPlaying && re == 5 && i < 10; ++i) { // LockRect失败时重复尝试,最多10次
re = m_Dxva2D3DRender.D3DSoftDisplayFrame(m_pOutBuffer, pAvFrame->width, pAvFrame->height, ShowRect);
Sleep(1);
}
}
catch (int re)
{
myprint("m_Dxva2D3DRender.InitD3DRender again");
if (m_Dxva2D3DRender.InitD3DRender(m_showWnd, m_pCodecCxt->width, m_pCodecCxt->height) == false)
{
myprint("m_Dxva2D3DRender.InitD3DRender again fail");
av_packet_unref(&pkt);
continue;
}
}
}
}
if (m_bCapture /*&& pkt.flags == 1*/ && img_convert_ctx)
{
sws_scale(img_convert_ctx, (const uint8_t* const*)pAvFrame->data,
pAvFrame->linesize, 0, m_pCodecCxt->height,
pFrameRGB->data, pFrameRGB->linesize);
SaveIFrameImage(pFrameRGB->data[0], m_pCodecCxt->width, m_pCodecCxt->height);
m_bCapture = false;
}
}
}
}
}
else if (pkt.stream_index == m_nAudioIndex)
{
num_stream_index_err = 0;
if (m_bSupportAudio) {
pktdata = pkt.data;
pktsize = pkt.size;
if (pktsize > 0)
{
int gotframe = 0;
if (nullptr == m_pAudioCodecCxt || nullptr == pAvFrame) {
myprint("m_pAudioCodecCxt == NULL || pAvFrame == NULL");
break;
}
len = avcodec_send_packet(m_pAudioCodecCxt, &pkt);
if (len != 0 && len != AVERROR(EAGAIN))
{
pktsize = 0;
myprint("avcodec_send_packet len < 0");
break;
}
auto data_size = av_get_bytes_per_sample(m_pAudioCodecCxt->sample_fmt);
if (data_size < 0) {
myprint("Failed to calculate data size\n");
break;
}
while (gotframe == 0 && !m_bQuit) {
gotframe = avcodec_receive_frame(m_pAudioCodecCxt, pAvFrame);
if (!gotframe)
{
if (bPushAudioToQueue == true && m_bEnableAudio && !m_bStopAudio)
{
audio_frame_t audioFrame;
numBytes = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
auto dstNbSamples = av_rescale_rnd(pAvFrame->nb_samples,
m_src_sample_rate,
pAvFrame->sample_rate,
AV_ROUND_ZERO);
int data_size = 0;
try
{
auto nb = swr_convert(m_pSwrContext,
(uint8_t**)outData,
dstNbSamples,
(const uint8_t**)pAvFrame->data,
pAvFrame->nb_samples);
data_size = av_samples_get_buffer_size(nullptr, m_channels_play, nb, AV_SAMPLE_FMT_S16, 1);
}
catch (const std::exception&)
{
m_bSupportAudio = false;
myprint("swr_convert throw err, set m_bSupportAudio false");
continue;
}
int copy_size = 0;
int copy_ptr = 0;
for (int isub = data_size; isub > 0; isub -= copy_size) {
if (isub > m_audio_buffer_notify_size) {
copy_size = m_audio_buffer_notify_size;
copy_ptr = data_size - isub;
}
else
copy_size = isub;
audioFrame.data_size = copy_size;
memcpy(audioFrame.data, outData[0] + copy_ptr, copy_size);
EnterCriticalSection(&m_lock);
m_AudioPlayQue.push(audioFrame);
LeaveCriticalSection(&m_lock);
}
}
}
}
}
}
}
else if (++num_stream_index_err > 20) {
myprint("pkt.stream_index unfind, %d",pkt.stream_index);
enableReConnect();
}
av_packet_unref(&pkt);
}
else {
if (++num_av_read_frame_err > 10) {
myprint("num_av_read_frame_err is more than 10");
enableReConnect();
}
}
}
if (m_bDecodeThreadRun) {
myprint("m_bDecodeThreadRun is true");
enableReConnect();
}
if (pAvFrame)
av_free(pAvFrame);
if (pFrameRGB)
av_free(pFrameRGB);
if (pBuffer)
av_free(pBuffer);
if (img_convert_ctx)
sws_freeContext(img_convert_ctx);
if (outData[0] && outData[1])
{
av_free(outData[0]);
av_free(outData[1]);
outData[0] = 0;
outData[1] = 0;
}
}
四、相关下载
链接: 可执行程序下载
链接: 源码下载