博客园第一篇——SDL2+FFmpeg 制作简单播放器与同步

博客园地址

原标题:博客园第一篇——SDL2+FFmpeg 制作简单播放器&同步,因为“&”字符无法被百度sitemap插件处理写入 XML,所以改动了标题。

SDL 2.0.3; FFmpeg 20140402 shared;

部分来自 http://blog.csdn.net/jiqiujia/article/details/22449131 的第二个例子,修正了一些问题。

原来的代码在第555行,SDL_Delay(50) 应该替换为 av_free_packet(&packet),这样既解决了迟滞问题还解决了一个坑爹的内存泄露问题。(我才不会告诉你我播放一个稍微大点的视频在8秒之后 2 GB 内存就被吃掉了呢!)

https://github.com/phamquy/FFmpeg-tutorial-samples/blob/master/tutorial06.c 也有一些帮助。

功能:

  1. 简单播放器;
  2. 音频重采样,大部分视频的音频部分都播放正常(也有少部分仍不正常的);
  3. 简单的同步。当前以音频流为基准,对于比较小(<120 MB)的 .mpg/.flv 文件还正常,其他的有各种问题,包括视频仍然过快,或者严重丢帧(建议打开控制台自己看)。

下面上代码。部分地方有注释。

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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
#define _CRT_SECURE_NO_WARNINGS

extern "C" {
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
}
#include "SDL.h"
#include "SDL_image.h"
#include "SDL_thread.h"

#include <iostream>
#include <queue>
using namespace std;

#pragma warning(disable: 4996)

#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"swscale.lib")
#pragma comment(lib,"swresample.lib")

#pragma comment(lib,"sdl2.lib")

#define SDL_AUDIO_BUFFER_SIZE (1152)
#define AVCODEC_MAX_AUDIO_FRAME_SIZE (192000)

#if (defined main && defined __MINGW__)
#undef main
#endif

static Uint8 *audio_chunk;
static Uint32 audio_len;
static Uint8 *audio_pos;

static int64_t audio_pts = 0;
static int64_t audio_dts = 0;
static int64_t video_pts = 0;
static int64_t video_dts = 0;

static AVFrame *g_pFrameYUV;

SDL_Thread *g_pVideoThread;
SDL_mutex *g_pVideoMutex;

static int quit = 0;
static int video_quit = 0;

typedef struct video_thread_params
{
SwsContext *sws_context;
SDL_Texture *texture;
SDL_Renderer *renderer;
AVCodecContext *vid_codec_context;
SDL_mutex *video_mutex;
}
video_thread_params;

int video_thread_proc(void *data);

void fill_audio(void *udata, Uint8 *stream, int len){
if (audio_len == 0)
return;
len = (len > audio_len ? audio_len : len);
SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);
audio_pos += len;
audio_len -= len;
}

int AudioResampling(AVCodecContext * audio_dec_ctx,
AVFrame * pAudioDecodeFrame,
int out_sample_fmt,
int out_channels,
int out_sample_rate,
uint8_t* out_buf)
{
SwrContext * swr_ctx = NULL;
int data_size = 0;
int ret = 0;
int64_t src_ch_layout = audio_dec_ctx->channel_layout;
int64_t dst_ch_layout = AV_CH_LAYOUT_STEREO;
int dst_nb_channels = 0;
int dst_linesize = 0;
int src_nb_samples = 0;
int dst_nb_samples = 0;
int max_dst_nb_samples = 0;
uint8_t **dst_data = NULL;
int resampled_data_size = 0;

swr_ctx = swr_alloc();
if (!swr_ctx)
{
printf("swr_alloc error \n");
return -1;
}

src_ch_layout = (audio_dec_ctx->channels ==
av_get_channel_layout_nb_channels(audio_dec_ctx->channel_layout)) ?
audio_dec_ctx->channel_layout :
av_get_default_channel_layout(audio_dec_ctx->channels);

if (out_channels == 1)
{
dst_ch_layout = AV_CH_LAYOUT_MONO;
//printf("dst_ch_layout: AV_CH_LAYOUT_MONO\n");
}
else if (out_channels == 2)
{
dst_ch_layout = AV_CH_LAYOUT_STEREO;
//printf("dst_ch_layout: AV_CH_LAYOUT_STEREO\n");
}
else
{
dst_ch_layout = AV_CH_LAYOUT_SURROUND;
//printf("dst_ch_layout: AV_CH_LAYOUT_SURROUND\n");
}

if (src_ch_layout <= 0)
{
printf("src_ch_layout error \n");
return -1;
}

src_nb_samples = pAudioDecodeFrame->nb_samples;
if (src_nb_samples <= 0)
{
printf("src_nb_samples error \n");
return -1;
}

av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
av_opt_set_int(swr_ctx, "in_sample_rate", audio_dec_ctx->sample_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", audio_dec_ctx->sample_fmt, 0);

av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
av_opt_set_int(swr_ctx, "out_sample_rate", out_sample_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", (AVSampleFormat)out_sample_fmt, 0);

if ((ret = swr_init(swr_ctx)) < 0) {
printf("Failed to initialize the resampling context\n");
return -1;
}

max_dst_nb_samples = dst_nb_samples = av_rescale_rnd(src_nb_samples,
out_sample_rate, audio_dec_ctx->sample_rate, AV_ROUND_UP);
if (max_dst_nb_samples <= 0)
{
printf("av_rescale_rnd error \n");
return -1;
}

dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels,
dst_nb_samples, (AVSampleFormat)out_sample_fmt, 0);
if (ret < 0)
{
printf("av_samples_alloc_array_and_samples error \n");
return -1;
}


dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, audio_dec_ctx->sample_rate) +
src_nb_samples, out_sample_rate, audio_dec_ctx->sample_rate, AV_ROUND_UP);
if (dst_nb_samples <= 0)
{
printf("av_rescale_rnd error \n");
return -1;
}
if (dst_nb_samples > max_dst_nb_samples)
{
av_free(dst_data[0]);
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
dst_nb_samples, (AVSampleFormat)out_sample_fmt, 1);
max_dst_nb_samples = dst_nb_samples;
}

if (swr_ctx)
{
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples,
(const uint8_t **)pAudioDecodeFrame->data, pAudioDecodeFrame->nb_samples);
if (ret < 0)
{
printf("swr_convert error \n");
return -1;
}

resampled_data_size = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
ret, (AVSampleFormat)out_sample_fmt, 1);
if (resampled_data_size < 0)
{
printf("av_samples_get_buffer_size error \n");
return -1;
}
}
else
{
printf("swr_ctx null error \n");
return -1;
}

memcpy(out_buf, dst_data[0], resampled_data_size);

if (dst_data)
{
av_freep(&dst_data[0]);
}
av_freep(&dst_data);
dst_data = NULL;

if (swr_ctx)
{
swr_free(&swr_ctx);
}
return resampled_data_size;
}

//创建一个全局的结构体变量以便于我们从文件中得到的声音包有地方存
//放同时也保证SDL中的声音回调函数audio_callback 能从这个地方得到声音数据
typedef struct PacketQueue{
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;//因为SDL 是在一个独立的线程中来进行音频处理的。如果我们没有正确的锁定这个队列,我们有可能把数据搞乱。
SDL_cond *cond;
}PacketQueue;

PacketQueue audioq;
PacketQueue videoq;
queue<AVFrame *> frameq;

void packet_queue_init(PacketQueue *pq){
memset(pq, 0, sizeof(PacketQueue));
pq->mutex = SDL_CreateMutex();
pq->cond = SDL_CreateCond();
}

int packet_queue_put(PacketQueue *q, AVPacket *pkt){
AVPacketList *pkt1;
/*(
if (av_dup_packet(pkt) < 0){
printf("error");
return -1;
}
*/

pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList));
if (!pkt1){
printf("error");
return -1;
}

av_copy_packet(&pkt1->pkt, pkt);
av_free_packet(pkt);
pkt1->next = NULL;

//函数SDL_LockMutex()锁定队列的互斥量以便于我们向队列中添加东西,然后函
//数SDL_CondSignal()通过我们的条件变量为一个接 收函数(如果它在等待)发
//出一个信号来告诉它现在已经有数据了,接着就会解锁互斥量并让队列可以自由
//访问。
SDL_LockMutex(q->mutex);

if (!q->last_pkt)//队列为空
q->first_pkt = pkt1;
else//队列不为空
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond);

SDL_UnlockMutex(q->mutex);

return 0;
}

int decode_interrupt_cb(void){
return quit;
}

static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block){
AVPacketList *pkt1;
int ret;

SDL_LockMutex(q->mutex);

for (;;){
if (quit){
ret = -1;
break;
}

pkt1 = q->first_pkt;
if (pkt1){
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
//*pkt = pkt1->pkt;
av_copy_packet(pkt, &pkt1->pkt);
av_free_packet(&pkt1->pkt);
av_free(pkt1);
ret = 1;
break;
}
else if (!block){
ret = 0;
break;
}
else{
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}

int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size){
static AVPacket pkt;
static uint8_t *audio_pkt_data = NULL;
static int audio_pkt_size = 0;

int len1, data_size, ret = 0;

static AVFrame *pFrame;
pFrame = av_frame_alloc();

/*if (packet_queue_get(&audioq, &pkt, 1) < 0){//从这里开始,取得main线程放入队列的包
printf("error, can't get packet from the queue");
return -1;
}

len1 = avcodec_decode_audio4(aCodecCtx, pFrame, &ret, &pkt);
if (len1 < 0)
return -1;

return AudioResampling(aCodecCtx, pFrame, AV_SAMPLE_FMT_S16, 2, 44100, audio_buf);*/
for (;;){
while (audio_pkt_size > 0){
data_size = buf_size;
len1 = avcodec_decode_audio4(aCodecCtx, pFrame, &ret, &pkt);

//len1 = avcodec_decode_audio3(aCodecCtx, (int16_t *)audio_buf,
// &data_size, &pkt);
if (len1 < 0){//if error, skip frame
printf("error\n");
audio_pkt_size = 0;
break;
}
data_size = AudioResampling(aCodecCtx, pFrame, AV_SAMPLE_FMT_S16, 2, 44100, audio_buf);
audio_pkt_data += len1;
audio_pkt_size -= len1;
if (data_size <= 0)//No data yet, get more frames
continue;
return data_size;
}
//if (pkt.data)
av_free_packet(&pkt);
if (quit)
return -1;
if (packet_queue_get(&audioq, &pkt, 1) < 0){//从这里开始,取得main线程放入队列的包
printf("error, can't get packet from the queue");
return -1;
}

//SDL_LockMutex(g_pVideoMutex);
audio_pts = pkt.pts;
audio_dts = pkt.dts;
//SDL_UnlockMutex(g_pVideoMutex);

audio_pkt_data = pkt.data;
audio_pkt_size = pkt.size;
}
}
//声音回调函数
//userdata是输入,stream是输出,len是输入,len的值一般为4096(调试中发现的),
//audio_callback函数的功能是调用audio_decode_frame函数,把解码后数据块audio_buf追加在stream的后面,
//通过SDL库对audio_callback的不断调用,不断解码数据,然后放到stream的末尾,
//SDL库认为stream中数据够播放一帧音频了,就播放它,
//第三个参数len是向stream中写数据的内存分配尺度,是分配给audio_callback函数写入缓存大小。
void audio_callback(void *userdata, Uint8 *stream, int len){
//SDL_memset(stream, 0, len);
AVCodecContext *aCodecCtx = (AVCodecContext*)userdata;
int len1, audio_size;

//audio_buf 的大小为 1.5 倍的声音帧的大 小以便于有一个比较好的缓冲
static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
static unsigned int audio_buf_size = 0;
static unsigned int audio_buf_index = 0;

while (len > 0){
if (audio_buf_index >= audio_buf_size){//already send all our data, get more
audio_size = audio_decode_frame(aCodecCtx, audio_buf, sizeof(audio_buf));
if (audio_size < 0){//error, output silence
printf("error, output silence\n");
audio_buf_size = SDL_AUDIO_BUFFER_SIZE;
memset(audio_buf, 0, audio_buf_size);
}
else
audio_buf_size = audio_size;
audio_buf_index = 0;
}
len1 = audio_buf_size - audio_buf_index;
if (len1>len){
len1 = len;
}
memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
len -= len1;
stream += len1;
audio_buf_index += len1;
}
}

// 视频流处理线程
// 这里对齐是以音频流为准的
// 所以如果音频流(相对于视频流)过于稀疏的话,就会失去作用,效果不好

int video_thread_proc(void *data)
{
video_thread_params *params = (video_thread_params *)data;
AVFrame *pFrame = NULL;
AVFrame *pNextFrame = NULL;
AVPacket packet = {0};
AVPacket nextpacket;

// 注意,以下代码比较的都是 DTS(解压缩时间戳)而不是 PTS(显示时间戳)!!!
// 实际视频显示是以 PTS 为准的,但是 PTS 大多没有规律(只要是在解压之后就好),难以处理
// 所幸在相当一部分视频中 DTS 与 PTS 相差较小,所以这里就用 DTS 了

while (!video_quit)
{
while (!frameq.empty())
{
if (pFrame == NULL)
{
SDL_LockMutex(params->video_mutex);

// 这里采用了“连续读取”的方法,也即如果下一帧的 DTS 小于当前基准(目前采用音频流),则循环直至找到第一个 DTS 比基准大的
// 然后播放小于基准而且离基准最近的帧,并缓存下一帧
// 由于处理时间较长,而且使用了互斥体(看,音频部分也用到了),所以可能有极少数的音频帧有异常(噪声啊之类的)
// 当然也可以将这一段注释掉,采用 pFrame = frameq.count(); frameq.pop();,同时解除 if (packet_queue_get()) 的注释(和下面的一对大括号)
// 但是无跳过的方法会导致视频严重滞后

if (pNextFrame != NULL)
{
pFrame = pNextFrame;
SDL_memcpy(&packet, &nextpacket, sizeof(AVPacket));
pNextFrame = NULL;
}
else
{
pFrame = frameq.front();
frameq.pop();
}
while (!frameq.empty())
{
pNextFrame = frameq.front();
frameq.pop();
packet_queue_get(&videoq, &nextpacket, 1);

if (nextpacket.dts <= audio_dts)
{
av_free_packet(&packet);
av_frame_free(&pFrame);
SDL_memcpy(&packet, &nextpacket, sizeof(AVPacket));
pFrame = pNextFrame;
pNextFrame = NULL;
}
else
{
break;
}
}


//pFrame = frameq.front();
//frameq.pop();


//cout << "vdts: " << packet.dts << " adts: " << audio_dts << endl;

//if (packet_queue_get(&videoq, &packet, 1) >= 0)
//{
sws_scale(params->sws_context, (const uint8_t* const*)pFrame->data,
pFrame->linesize, 0, params->vid_codec_context->height, g_pFrameYUV->data, g_pFrameYUV->linesize);

SDL_UpdateYUVTexture(params->texture, NULL, g_pFrameYUV->data[0], g_pFrameYUV->linesize[0],
g_pFrameYUV->data[1], g_pFrameYUV->linesize[1], g_pFrameYUV->data[2], g_pFrameYUV->linesize[2]);

//SDL_RenderClear(params->renderer);
SDL_RenderCopy(params->renderer, params->texture, NULL, NULL);
SDL_RenderPresent(params->renderer);
// 可以使用 av_frame_clone() + 队列 实现“远程”dts 读取
//if (params->vid_codec_context->refcounted_frames)
//{
// av_frame_unref(pFrame);
//}
//}

//cout << "--------------------------------------------------" << endl;
//cout << "vidpts: " << packet.pts << " audpts: " << audio_pts << endl;
//cout << "viddts: " << packet.dts << " auddts: " << audio_dts << endl;

SDL_UnlockMutex(params->video_mutex);
}
else
{
//cout << "vdts: " << packet.dts << " adts: " << audio_dts << endl;

// 如果当前帧应该用被缓存的帧,那就用,不要重新读取了

sws_scale(params->sws_context, (const uint8_t* const*)pFrame->data,
pFrame->linesize, 0, params->vid_codec_context->height, g_pFrameYUV->data, g_pFrameYUV->linesize);

SDL_UpdateYUVTexture(params->texture, NULL, g_pFrameYUV->data[0], g_pFrameYUV->linesize[0],
g_pFrameYUV->data[1], g_pFrameYUV->linesize[1], g_pFrameYUV->data[2], g_pFrameYUV->linesize[2]);

//SDL_RenderClear(params->renderer);
SDL_RenderCopy(params->renderer, params->texture, NULL, NULL);
SDL_RenderPresent(params->renderer);
// 可以使用 av_frame_clone() + 队列 实现“远程”dts 读取
if (params->vid_codec_context->refcounted_frames)
{
av_frame_unref(pFrame);
}

// 如果该帧是在音频帧之前的,那就销毁它,和数据包
if (packet.dts <= audio_dts)
{
av_frame_free(&pFrame);
av_free_packet(&packet);
pFrame = NULL;
}
}

}
}

return 0;
}

int main(int argc, char *argv[])
{
av_register_all(); //注册了所有的文件格式和编解码的库,它们将被自动的使用在被打开的合适格式的文件上
AVFormatContext *pFormatCtx;
pFormatCtx = avformat_alloc_context();

if (argc < 2) return 0;
//Open an input stream and read the header
if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0){
printf("Can't open the file\n");
return -1;
}
//Retrieve stream information
if (avformat_find_stream_info(pFormatCtx, NULL) < 0){
printf("Couldn't find stream information.\n");
return -1;
}

//output file information
cout << "文件信息----------------------------------" << endl;
av_dump_format(pFormatCtx, 0, argv[1], 0);
cout << "--------------------------------------------" << endl;

int i, videoIndex, audioIndex;

//Find the first video stream
videoIndex = -1;
audioIndex = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++){//视音频流的个数
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO
&& videoIndex < 0){
videoIndex = i;
}
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO
&& audioIndex < 0)
audioIndex = i;
}

if (videoIndex == -1)
return -1;
if (audioIndex == -1)
return -1;

AVCodecContext *pCodecCtx, *paCodecCtx;
AVCodec *pCodec, *paCodec;
//Get a pointer to the codec context for the video stream
//流中关于编解码器的信息就是被我们叫做"codec context"(编解码器上下文)
//的东西。这里面包含了流中所使用的关于编解码器的所有信
pCodecCtx = pFormatCtx->streams[videoIndex]->codec;
// 帧引用:打开,见 AVFrame 的注释
pCodecCtx->refcounted_frames = 1;
paCodecCtx = pFormatCtx->streams[audioIndex]->codec;
//Find the decoder for the video stream
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
paCodec = avcodec_find_decoder(paCodecCtx->codec_id);

if (pCodec == NULL || paCodecCtx == NULL){
printf("Unsupported codec!\n");
return -1;
}
//Open codec
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
printf("Could not open video codec.\n");
return -1;
}
if (avcodec_open2(paCodecCtx, paCodec, NULL) < 0){
printf("Could not open audio codec.\n");
return -1;
}

//--------------------------------------------------------//

printf("比特率 %3d\n", pFormatCtx->bit_rate);
printf("解码器名称 %s\n", paCodecCtx->codec->long_name);
printf("time_base %d \n", paCodecCtx->time_base);
printf("声道数 %d \n", paCodecCtx->channels);
printf("sample per second %d \n", paCodecCtx->sample_rate);
//--------------------------------------------------------//

//allocate video frame and set its fileds to default value
AVFrame *pFrame;
//AVFrame *pFrameYUV;
pFrame = av_frame_alloc();
g_pFrameYUV = av_frame_alloc();

//即使我们申请了一帧的内存,当转换的时候,我们仍然需要一个地方来放置原始
//的数据。我们使用avpicture_get_size 来获得我们需要的大小, 然后手工申请
//内存空间:
uint8_t *out_buffer;
int numBytes;
numBytes = avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
//av_malloc 是ffmpeg 的malloc,用来实现一个简单的malloc 的包装,这样来保
//证内存地址是对齐的(4 字节对齐或者2 字节对齐)。它并不能保 护你不被内
//存泄漏,重复释放或者其它malloc 的问题所困扰。
out_buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
//Assign appropriate parts of buffer to image planes in pFrameYUV
//Note that pFrameYUV is an AVFrame, but AVFrame is a superset of AVPicture
avpicture_fill((AVPicture*)g_pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

//----------------SDL--------------------------------------//
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){
printf("Could not initialize SDL -%s\n", SDL_GetError());
exit(1);
}
//先设置声音的选项:采样率,声音通道数和其它的参 数,然后我们
//设置一个回调函数和一些用户数据userdata。当开始播放音频的时候,SDL 将不
//断地调用这个回调函数并且要求它来向声音缓冲填入一个特定的数量的字节。
//当我们把这些信息放到SDL_AudioSpec 结构体中后,我们调用函数
//SDL_OpenAudio()就会打开声音设备并且给我们送 回另外一个AudioSpec 结构
//体。这个结构体是我们实际上用到的--因为我们不能保证得到我们所要求的。
SDL_AudioSpec wanted_spec;
wanted_spec.freq = paCodecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = paCodecCtx->channels; //声音的通道数
wanted_spec.silence = 0; //用来表示静音的值
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE; //声音缓冲区的大小
wanted_spec.callback = audio_callback;
wanted_spec.userdata = paCodecCtx;

if (SDL_OpenAudio(&wanted_spec, NULL) < 0){
printf("SDL_OpenAudio error: %s\n", SDL_GetError());
return -1;
}

packet_queue_init(&audioq);
packet_queue_init(&videoq);
SDL_PauseAudio(0);

SDL_Window *window = nullptr;
window = SDL_CreateWindow("MPlayer", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
pCodecCtx->width, pCodecCtx->height, SDL_WINDOW_SHOWN);
if (!window){
cout << SDL_GetError() << endl;
return 1;
}

SDL_Renderer *ren = nullptr;
ren = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (ren == nullptr){
cout << SDL_GetError() << endl;
return -1;
}

SDL_Texture *texture = nullptr;
texture = SDL_CreateTexture(ren, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);

//*************************************************************//
//通过读取包来读取整个视频流,然后把它解码成帧,最后转换格式并且保存
int frameFinished;
//int psize = pCodecCtx->width * pCodecCtx->height;
AVPacket packet;
av_new_packet(&packet, numBytes);

i = 0;
int ret;
static struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);

SDL_Event ev;

video_thread_params vtp;
vtp.renderer = ren;
vtp.texture = texture;
vtp.sws_context = img_convert_ctx;
vtp.vid_codec_context = pCodecCtx;
vtp.video_mutex = SDL_CreateMutex();
g_pVideoMutex = vtp.video_mutex;
g_pVideoThread = SDL_CreateThread(video_thread_proc, "video_thread", &vtp);

double v_a_ratio; // 视频帧数/音频帧数
int frame_queue_size;

//Read the next frame of a stream
while ((!quit) && (av_read_frame(pFormatCtx, &packet) >= 0 || (!frameq.empty())))
{
//Is this a packet from the video stream?
if (packet.stream_index == videoIndex)
{
//decode video frame of size packet.size from packet.data into picture
ret = avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
//Did we get a video frame?
if (ret >= 0)
{
//Convert the image from its native format to YUV
if (frameFinished)
{
/*
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, g_pFrameYUV->data, g_pFrameYUV->linesize);

SDL_UpdateYUVTexture(texture, NULL, g_pFrameYUV->data[0], g_pFrameYUV->linesize[0],
g_pFrameYUV->data[1], g_pFrameYUV->linesize[1], g_pFrameYUV->data[2], g_pFrameYUV->linesize[2]);

AVFrame *pNewFrame = av_frame_clone(pFrame);
frameq.push(pNewFrame);
packet_queue_put(&videoq, &packet);

SDL_RenderClear(ren);
SDL_RenderCopy(ren, texture, NULL, NULL);
SDL_RenderPresent(ren);
// 可以使用 av_frame_clone() + 队列 实现远程 dts 读取
if (pCodecCtx->refcounted_frames)
{
av_frame_unref(pFrame);
}
cout << "<<<<<<" << endl;
cout << "vpts: " << packet.pts << ", apts: " << audio_pts << endl;
cout << "vdts: " << packet.dts << ", adts: " << audio_dts << endl;
*/
// 用互斥体保持同步,将包送入队列,让另外的线程自己处理
SDL_LockMutex(vtp.video_mutex);
packet_queue_put(&videoq, &packet);
AVFrame *pNewFrame = av_frame_clone(pFrame);
frameq.push(pNewFrame);
//cout << "Pushing vpacket." << endl;
SDL_UnlockMutex(vtp.video_mutex);
}
// 注意这里也必须要 free packet,否则会导致严重内存泄露
// 我修改了 packet_queue_put() 函数,它会复制 packet,所以可以放心释放上
av_free_packet(&packet);
}
else{
av_free_packet(&packet);
cout << "decode error" << endl;
return -1;
}
}
else if (packet.stream_index == audioIndex){
//packet_queue_put(&audioq, &packet);
/*ret = avcodec_decode_audio4(paCodecCtx, pFrame, &frameFinished, &packet);
cout << pFrame->format << endl;

if (ret < 0){
printf("Error in decoding audio frame\n");
exit(0);
}
if (frameFinished){
printf("pts %5d\n", packet.pts);
printf("dts %5d\n", packet.dts);
printf("packet_size %5d\n", packet.size);
}
audio_chunk = (Uint8*)pFrame->data[0];
audio_len = pFrame->linesize[0];

audio_pos = audio_chunk;
//SDL_PauseAudio(0);
while (audio_len>0)
SDL_Delay(1);*/
packet_queue_put(&audioq, &packet);
}
else
{
av_free_packet(&packet);
}

process_sdl_events:
if (SDL_PollEvent(&ev))
{
switch (ev.type){
case SDL_QUIT:
{
quit = 1;
video_quit = 1;
SDL_Quit();
goto exit_line;
break;
}
case SDL_KEYDOWN:
if (ev.key.keysym.scancode == SDL_SCANCODE_ESCAPE)
{
quit = 1;
video_quit = 1;
SDL_Quit();
goto exit_line;
break;
}
default:
break;
}
}

//cout << "vframes: " << pCodecCtx->frame_number << " aframes: " << paCodecCtx->frame_number << endl;

// 设置一个缓冲区大小,如果超过此大小则暂停处理(视频和音频)帧
// 这里采用限制 frameq 的大小的方法
// 如果采用以下的动态代码,有可能导致结尾“崩坏”,囧
/*
if (paCodecCtx->frame_number == 0)
{
v_a_ratio = 300; // 一个很大的值,基本上能保证所有视频都能解码
}
else
{
v_a_ratio = pCodecCtx->frame_number / (double)paCodecCtx->frame_number;
if (v_a_ratio < 10.0) v_a_ratio = 10.0;
}

frame_queue_size = (int)v_a_ratio * 2;
if (frameq.size() > frame_queue_size) goto process_sdl_events;
*/
if (frameq.size() > 50) goto process_sdl_events;
}

exit_line:

SDL_DestroyMutex(vtp.video_mutex);

SDL_DestroyTexture(texture);

av_frame_free(&pFrame);
av_frame_free(&g_pFrameYUV);

avcodec_close(pCodecCtx);

avformat_close_input(&pFormatCtx);

return 0;
}
分享到 评论