FFmpeg4入门系列教程19:简单视频播放器

索引地址:系列教程索引地址

上一篇:FFmpeg4入门系列教程18:pcm编码为mp3

音视频结合

FFmpeg4入门系列教程8:软解并使用QtWidget播放视频(YUV420P->RGB32)FFmpeg4入门系列教程17:软件解码音频并使用QAudioOutput播放结合起来,就是最简单的视频播放器了。

先将第8篇教程的源码复制过来,并改名为audio_video_sync

然后将第17篇中的源码合进来,不同的是第17篇是直接打开文件,而在此需要读取音频流。

将音频处理的部分拆分为一个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
FFmpegAudio::FFmpegAudio()
{
fmtCtx = avformat_alloc_context();
pkt = av_packet_alloc();
audioFrame = av_frame_alloc();

QAudioFormat audioFmt;
audioFmt.setSampleRate(44100);
audioFmt.setChannelCount(2);
audioFmt.setSampleSize(16);
audioFmt.setCodec("audio/pcm");
audioFmt.setByteOrder(QAudioFormat::LittleEndian);
audioFmt.setSampleType(QAudioFormat::SignedInt);

QAudioDeviceInfo info = QAudioDeviceInfo::defaultOutputDevice();
if(!info.isFormatSupported(audioFmt)){
audioFmt = info.nearestFormat(audioFmt);
}
audioOutput = new QAudioOutput(audioFmt);
audioOutput->setVolume(100);

streamOut = audioOutput->start();
}

FFmpegAudio::~FFmpegAudio()
{
if(streamOut->isOpen()){
audioOutput->stop();
streamOut->close();
}
if(!pkt) av_packet_free(&pkt);
if(!audioCodecCtx) avcodec_free_context(&audioCodecCtx);
if(!audioCodecCtx) avcodec_close(audioCodecCtx);
if(!fmtCtx) avformat_close_input(&fmtCtx);
}

void FFmpegAudio::setUrl(QString url)
{
_url = url;
}

int FFmpegAudio::open_input_file()
{
if(_url.isEmpty()) return -1;

if(avformat_open_input(&fmtCtx,_url.toLocal8Bit().data(),NULL,NULL)<0){
printf("Cannot open input file.\n");
return -1;
}

if(avformat_find_stream_info(fmtCtx,NULL)<0){
printf("Cannot find any stream in file.\n");
return -1;
}

int streamCnt=fmtCtx->nb_streams;
for(int i=0;i<streamCnt;i++){
if(fmtCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO){
audioStreamIndex=(int)i;
continue;
}
}

if(audioStreamIndex==-1){
printf("Cannot find any stream in file.\n");
return -1;
}

///////////////////////////音频部分开始//////////////////////////////////
AVCodecParameters *audioCodecPara = fmtCtx->streams[audioStreamIndex]->codecpar;

if(!(audioCodec = avcodec_find_decoder(audioCodecPara->codec_id))){
printf("Cannot find valid audio decode codec.\n");
return -1;
}

if(!(audioCodecCtx = avcodec_alloc_context3(audioCodec))){
printf("Cannot find valid audio decode codec context.\n");
return -1;
}

if(avcodec_parameters_to_context(audioCodecCtx,audioCodecPara)<0){
printf("Cannot initialize audio parameters.\n");
return -1;
}

audioCodecCtx->pkt_timebase = fmtCtx->streams[audioStreamIndex]->time_base;

if(avcodec_open2(audioCodecCtx,audioCodec,NULL)<0){
printf("Cannot open audio codec.\n");
return -1;
}

//设置转码参数
uint64_t out_channel_layout = audioCodecCtx->channel_layout;
out_sample_rate = audioCodecCtx->sample_rate;
out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
//printf("out rate : %d , out_channel is: %d\n",out_sample_rate,out_channels);

audio_out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE*2);

swr_ctx = swr_alloc_set_opts(NULL,
out_channel_layout,
out_sample_fmt,
out_sample_rate,
audioCodecCtx->channel_layout,
audioCodecCtx->sample_fmt,
audioCodecCtx->sample_rate,
0,NULL);
swr_init(swr_ctx);
///////////////////////////音频部分结束//////////////////////////////////

return true;
}

void FFmpegAudio::run()
{
if(!open_input_file()){
qDebug()<<"Please open file first.";
return;
}

qDebug()<<"Open file "<<_url<<"done.";

double sleep_time=0;

while(av_read_frame(fmtCtx,pkt)>=0){
qDebug()<<"read audio frame";
if(pkt->stream_index==audioStreamIndex){
qDebug()<<"audio";
if(avcodec_send_packet(audioCodecCtx,pkt)>=0){
while(avcodec_receive_frame(audioCodecCtx,audioFrame)>=0){
qDebug()<<"receive";
if(av_sample_fmt_is_planar(audioCodecCtx->sample_fmt)){
int len = swr_convert(swr_ctx,
&audio_out_buffer,
MAX_AUDIO_FRAME_SIZE*2,
(const uint8_t**)audioFrame->data,
audioFrame->nb_samples);
if(len<=0){
continue;
}
//qDebug("convert length is: %d.\n",len);

int out_size = av_samples_get_buffer_size(0,
out_channels,
len,
out_sample_fmt,
1);

sleep_time=(out_sample_rate*16*2/8)/out_size;

if(audioOutput->bytesFree()<out_size){
msleep(sleep_time);
streamOut->write((char*)audio_out_buffer,out_size);
}else {
streamOut->write((char*)audio_out_buffer,out_size);
}
}
}
}
av_packet_unref(pkt);
}
}

qDebug()<<"All video play done";
}

然后在播放视频的时候启动这个线程

1
2
3
fa = new FFmpegAudio;
connect(fa,&FFmpegAudio::finished,fa,&FFmpegAudio::deleteLater);
fa->start();

效果为:

最最简单的播放器(没有进度条、没有快进快退、只有最简单的视频播放功能),至于音视频同步的更高级用法需要自己去探索,毕竟只是入门。

主要是本系列持续的时间太长了,看久了有点腻,而且我还有别的工作要做,等到我有空再深入研究吧。

下一篇:FFmpeg4入门系列教程20:视频添加滤镜