FFMPEG音視訊同步-音視訊實時采集編碼封裝
//-------------------------------------------------------------------------------------------------
參考連結1、https://blog.csdn.net/leixiaohua1020/article/details/39702113
參考連結2、https://blog.csdn.net/li_wen01/article/details/67631687
//-------------------------------------------------------------------------------------------------
音視訊同步錄制相關文章
//-------------------------------------------------------------------------------------------------
1、 ffmpeg-攝像頭采集儲存
2、 ffmpeg-攝像頭采集編碼封裝
3、 ffmpeg-音頻正弦産生并編碼封裝
4、 ffmpeg-音頻實時采集儲存
5、 ffmpeg-音頻實時采集編碼封裝
6、 ffmpeg-音視訊實時采集編碼封裝
7、 ffmpeg音視訊同步-音視訊實時采集編碼推流
8、 ffmpeg音視訊同步-音視訊實時采集編碼推流-優化版本
//---------------------------------------------------------------
系統環境:
系統版本:lubuntu 16.04
Ffmpge版本:ffmpeg version N-93527-g1125277
攝像頭:1.3M HD WebCan
虛拟機:Oracle VM VirtualBox 5.2.22
指令檢視裝置 ffmpeg -devices
本章文檔基于《ffmpeg-攝像頭采集編碼封裝》和《ffmpeg-音頻實時采集編碼封裝》。在同一程序中,判斷其産生的time=pts*time_base,根據其視訊的幀率,以及音頻産生的采樣率等,來比較目前幀時間time,來寫入音視訊。
1.簡介
FFmpeg中有一個和多媒體裝置互動的類庫:Libavdevice。使用這個庫可以讀取電腦(或者其他裝置上)的多媒體裝置的資料,或者輸出資料到指定的多媒體裝置上。
1.1資料流程圖

1.2 代碼流程圖
1.3 隊列傳輸流程圖
2.源碼
最簡單的基于Libavdevice的音頻采集口資料讀取一幀幀pcm資料,經過音頻重采樣擷取目标AAC的音頻源資料參數,同時基于Libavdevice的視訊采集口,擷取yuv420資料,再經過編碼,封裝等,儲存成FLV檔案。
程式主要是參考/doc/example/muxing.c源碼的音視訊同步方法。
2.1音頻初始化
1. int open_audio_capture()
2. {
3.
4. printf("open_audio_capture\n");
5.
6. //********add alsa read***********//
7. AVCodecContext *pCodecCtx;
8. AVCodec *pCodec;
9. AVFormatContext *a_ifmtCtx;
10. int i,ret;
11. //Register Device
12. avdevice_register_all();
13.
14. a_ifmtCtx = avformat_alloc_context();
15.
16.
17. //Linux
18. AVInputFormat *ifmt=av_find_input_format("alsa");
19. if(avformat_open_input(&a_ifmtCtx,"default",ifmt,NULL)!=0){
20. printf("Couldn't open input stream.default\n");
21. return -1;
22. }
23.
24.
25. if(avformat_find_stream_info(a_ifmtCtx,NULL)<0)
26. {
27. printf("Couldn't find stream information.\n");
28. return -1;
29. }
30.
31. int audioindex=-1;
32. for(i=0; i<a_ifmtCtx->nb_streams; i++)
33. if(a_ifmtCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO)
34. {
35. audioindex=i;
36. break;
37. }
38. if(audioindex==-1)
39. {
40. printf("Couldn't find a video stream.\n");
41. return -1;
42. }
43.
44. pCodecCtx=a_ifmtCtx->streams[audioindex]->codec;
45. pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
46. if(pCodec==NULL)
47. {
48. printf("Codec not found.\n");
49. return -1;
50. }
51. if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
52. {
53. printf("Could not open codec.\n");
54. return -1;
55. }
56.
57. AVPacket *in_packet=(AVPacket *)av_malloc(sizeof(AVPacket));
58.
59. AVFrame *pAudioFrame=av_frame_alloc();
60. if(NULL==pAudioFrame)
61. {
62. printf("could not alloc pAudioFrame\n");
63. return -1;
64. }
65.
66. //audio output paramter //resample
67. uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;
68. int out_sample_fmt = AV_SAMPLE_FMT_S16;
69. int out_nb_samples =1024; //pCodecCtx->frame_size;
70. int out_sample_rate = 48000;
71. int out_nb_channels = av_get_channel_layout_nb_channels(out_channel_layout);
72. int out_buffer_size = av_samples_get_buffer_size(NULL, out_nb_channels, out_nb_samples, out_sample_fmt, 1);
73. uint8_t *dst_buffer=NULL;
74. dst_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE);
75. int64_t in_channel_layout = av_get_default_channel_layout(pCodecCtx->channels);
76.
77.
78. printf("audio sample_fmt=%d size=%d channel=%d sample_rate=%d in_channel_layout=%s\n",
79. pCodecCtx->sample_fmt, pCodecCtx->frame_size,
80. pCodecCtx->channels,pCodecCtx->sample_rate,av_ts2str(in_channel_layout));
81.
82. struct SwrContext *audio_convert_ctx = NULL;
83. audio_convert_ctx = swr_alloc();
84. if (audio_convert_ctx == NULL)
85. {
86. printf("Could not allocate SwrContext\n");
87. return -1;
88. }
89.
90. /* set options */
91. av_opt_set_int (audio_convert_ctx, "in_channel_count", pCodecCtx->channels, 0);
92. av_opt_set_int (audio_convert_ctx, "in_sample_rate", pCodecCtx->sample_rate, 0);
93. av_opt_set_sample_fmt(audio_convert_ctx, "in_sample_fmt", pCodecCtx->sample_fmt, 0);
94. av_opt_set_int (audio_convert_ctx, "out_channel_count", out_nb_channels, 0);
95. av_opt_set_int (audio_convert_ctx, "out_sample_rate", out_sample_rate, 0);
96. av_opt_set_sample_fmt(audio_convert_ctx, "out_sample_fmt", out_sample_fmt, 0);
97.
98. /* initialize the resampling context */
99. if ((ret = swr_init(audio_convert_ctx)) < 0) {
100. fprintf(stderr, "Failed to initialize the resampling context\n");
101. exit(1);
102. }
103.
104.
105. alsa_input.in_packet=in_packet;
106. alsa_input.pCodecCtx=pCodecCtx;
107. alsa_input.pCodec=pCodec;
108. alsa_input.a_ifmtCtx=a_ifmtCtx;
109. alsa_input.audioindex=audioindex;
110. alsa_input.pAudioFrame=pAudioFrame;
111. alsa_input.audio_convert_ctx=audio_convert_ctx;
112. alsa_input.dst_buffer=dst_buffer;
113. alsa_input.out_buffer_size=out_buffer_size;
114. alsa_input.bCap=1;
115.
116. //******************************//
117. }
2.2 視訊初始化
1. int open_video_capture()
2. {
3. int i,ret;
4. printf("open_video_capture\n");
5.
6. //********add camera read***********//
7. AVCodecContext *pCodecCtx;
8. AVCodec *pCodec;
9. AVFormatContext *v_ifmtCtx;
10.
11. //Register Device
12. avdevice_register_all();
13.
14. v_ifmtCtx = avformat_alloc_context();
15.
16.
17. //Linux
18. AVInputFormat *ifmt=av_find_input_format("video4linux2");
19. if(avformat_open_input(&v_ifmtCtx,"/dev/video0",ifmt,NULL)!=0){
20. printf("Couldn't open input stream./dev/video0\n");
21. return -1;
22. }
23.
24.
25. if(avformat_find_stream_info(v_ifmtCtx,NULL)<0)
26. {
27. printf("Couldn't find stream information.\n");
28. return -1;
29. }
30.
31. int videoindex=-1;
32. for(i=0; i<v_ifmtCtx->nb_streams; i++)
33. if(v_ifmtCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
34. {
35. videoindex=i;
36. break;
37. }
38. if(videoindex==-1)
39. {
40. printf("Couldn't find a video stream.\n");
41. return -1;
42. }
43.
44. pCodecCtx=v_ifmtCtx->streams[videoindex]->codec;
45. pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
46. if(pCodec==NULL)
47. {
48. printf("Codec not found.\n");
49. return -1;
50. }
51. if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
52. {
53. printf("Could not open codec.\n");
54. return -1;
55. }
56.
57. AVFrame *pFrame,*pFrameYUV;
58. pFrame=av_frame_alloc();
59. pFrameYUV=av_frame_alloc();
60. unsigned char *out_buffer=(unsigned char *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
61. avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
62.
63. printf("camera width=%d height=%d \n",pCodecCtx->width, pCodecCtx->height);
64.
65.
66. struct SwsContext *img_convert_ctx;
67. img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
68. AVPacket *in_packet=(AVPacket *)av_malloc(sizeof(AVPacket));
69.
70.
71. video_input.img_convert_ctx=img_convert_ctx;
72. video_input.in_packet=in_packet;
73.
74. video_input.pCodecCtx=pCodecCtx;
75. video_input.pCodec=pCodec;
76. video_input.v_ifmtCtx=v_ifmtCtx;
77. video_input.videoindex=videoindex;
78. video_input.pFrame=pFrame;
79. video_input.pFrameYUV=pFrameYUV;
80. video_input.bCap=1;
81.
82. //******************************//
83. }
2.3輸出初始化
1. int open_output( const char *filename,AVDictionary *opt)
2. {
3.
4. printf("open_output\n");
5. static OutputStream video_st = { 0 }, audio_st = { 0 };
6.
7. AVOutputFormat *fmt;
8. AVFormatContext *oc;
9. AVCodec *audio_codec, *video_codec;
10. int ret;
11. int have_video = 0, have_audio = 0;
12. int encode_video = 0, encode_audio = 0;
13.
14.
15. /* allocate the output media context */
16. avformat_alloc_output_context2(&oc, NULL, NULL, filename);
17. if (!oc) {
18. printf("Could not deduce output format from file extension: using MPEG.\n");
19. avformat_alloc_output_context2(&oc, NULL, "mpeg", filename);
20. }
21. if (!oc)
22. return 1;
23.
24. fmt = oc->oformat;
25.
26. /* Add the audio and video streams using the default format codecs
27. * and initialize the codecs. */
28. if (fmt->video_codec != AV_CODEC_ID_NONE) {
29. add_stream(&video_st, oc, &video_codec, fmt->video_codec);
30. have_video = 1;
31. encode_video = 1;
32. }
33. if (fmt->audio_codec != AV_CODEC_ID_NONE) {
34. add_stream(&audio_st, oc, &audio_codec, AV_CODEC_ID_AAC);//fmt->audio_codec);
35. have_audio = 1;
36. encode_audio = 1;
37. }
38.
39. /* Now that all the parameters are set, we can open the audio and
40. * video codecs and allocate the necessary encode buffers. */
41. if (have_video)
42. open_video(oc, video_codec, &video_st, opt);
43.
44. if (have_audio)
45. open_audio(oc, audio_codec, &audio_st, opt);
46.
47. av_dump_format(oc, 0, filename, 1);
48.
49. /* open the output file, if needed */
50. if (!(fmt->flags & AVFMT_NOFILE)) {
51. ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
52. if (ret < 0) {
53. fprintf(stderr, "Could not open '%s': %s\n", filename,
54. av_err2str(ret));
55. return 1;
56. }
57. }
58.
59. /* Write the stream header, if any. */
60. ret = avformat_write_header(oc, &opt);
61. if (ret < 0) {
62. fprintf(stderr, "Error occurred when opening output file: %s\n",
63. av_err2str(ret));
64. return 1;
65. }
66.
67.
68. output_dev.encode_audio=encode_audio;
69. output_dev.encode_video=encode_video;
70. output_dev.oc=oc;
71. output_dev.have_audio=have_audio;
72. output_dev.have_video=have_video;
73. output_dev.video_st=&video_st;
74. output_dev.audio_st=&audio_st;
75.
76. }
2.4音頻采集線程
1. int audioThreadProc(void *arg)
2. {
3. int got_pic;
4. while(alsa_input.bCap)
5. {
6.
7. //printf("audioThreadProc running\n");
8.
9. AVPacket *pkt=get_audio_pkt(output_dev.audio_st,&alsa_input);
10. if(pkt==NULL) //從alsa中擷取pkt音頻源資料包
11. {
12. alsa_input.bCap =0;
13.
14. }
15. else
16. {
17. packet_queue_put(&output_dev.audioq,pkt,output_dev.audio_st->next_pts); //将擷取的資料包發送到傳輸隊列當中
18. }
19.
20.
21. }
22.
23. printf("videoThreadProc exit\n");
24. usleep(1000000);
25. return 0;
26.
27. }
2.5視訊采集線程
1. int videoThreadProc(void *arg)
2. {
3. int got_pic;
4. while(video_input.bCap)
5. {
6.
7.
8. AVPacket * pkt=get_video_pkt(output_dev.video_st,&video_input);
9. //從V4L中擷取視訊源資料包pkt
10. if(pkt==NULL)
11. {
12. //packet_queue_put_nullpacket(&output_dev.videoq,0);
13. video_input.bCap =0;
14.
15. }
16. else
17. {
18. packet_queue_put(&output_dev.videoq,pkt,output_dev.video_st->next_pts); //将擷取到的資料包發送到視訊傳輸隊列中
19. }
20.
21.
22.
23. }
24.
25. printf("videoThreadProc exit\n");
26. usleep(1000000);
27. return 0;
28.
29. }
2.6主程序
1. while (output_dev.encode_video || output_dev.encode_audio) { //判斷程序是否退出
2. if (output_dev.encode_video &&
3. (!output_dev.encode_audio || av_compare_ts(frame_pts, output_dev.video_st->enc->time_base,
4. frame_audio_pts, output_dev.audio_st->enc->time_base) <= 0)) //比較音頻視訊産生是的pts* time_base大小,以音頻pts*times_base為基準,若視訊的pts*time_base小于音頻,則寫入視訊幀,否則寫入音頻幀
5. {
6. if(packet_queue_get(&output_dev.videoq,&pkt,0,&frame_pts)<0) //擷取隊列中的視訊pkt
7. {
8. printf("packet_queue_get Error.\n");
9. break;
10. }
11.
12. if(flush_pkt.data== pkt.data)
13. {
14. printf("get pkt flush_pkt\n");
15. continue;
16. }
17.
18.
19. vframe=get_video_pkt2Frame(output_dev.video_st,&video_input,&pkt,&got_pic,frame_pts); //解碼pkt 成frame
20. if(!got_pic)
21. {
22. av_free_packet(&pkt);
23. printf("get_video_pkt2Frame error\n");
24. usleep(10000);
25. continue;
26. }
27. av_free_packet(&pkt);
28.
29. WRITE_FRAME:
30. output_dev.encode_video = !write_video_frame1(output_dev.oc, output_dev.video_st,vframe); //編碼frame成pkt,并且寫入封裝
31. //usleep(300000);
32. }
33. else//audio
34. {if(packet_queue_get(&output_dev.audioq,&audio_pkt,0,&frame_audio_pts)<0) //擷取隊列中的音頻pkt
35.
36. {
37. printf("packet_queue_get Error.\n");
38. break;
39. }
40.
41. if(flush_pkt.data== audio_pkt.data)
42. {
43. printf("get pkt flush_pkt\n");
44. continue;
45. }
46. //av_free_packet(&audio_pkt);
47.
48. #if 1
49.
50.
51. aframe=get_audio_pkt2Frame(output_dev.audio_st,&alsa_input,&audio_pkt,&got_pcm,frame_audio_pts); //解碼pkt 成frame
52. if(!got_pcm)
53. {
54. av_free_packet(&audio_pkt);
55. printf("get_video_pkt2Frame error\n");
56. usleep(10000);
57. continue;
58. }
59. av_free_packet(&audio_pkt);
60.
61. WRITE_AUDIO_FRAME:
62. output_dev.encode_audio = !write_audio_frame1(output_dev.oc, output_dev.audio_st,aframe); //編碼frame成pkt,并且寫入封裝
63.
64. //usleep(300000);
65. #endif
66. }
67.
68.
69.
70.
71. }
3.驗證
3.1編譯
1. #!/bin/sh
2. CC=gcc
3. SRCS=$(wildcard *.c */*.c)
4. OBJS=$(patsubst %.c, %.o, $(SRCS))
5. FLAG=-g
6. #LIB=-lavutil -lavformat -lavcodec -lavutil -lswscale -lswresample -lSDL2
7.
8.
9.
10. LIB=-lSDL2 -lSDLmain -I/usr/include/SDL -D_GNU_SOURCE=1 -D_REENTRANT -L/usr/lib/i386-linux-gnu -lSDL -I./\
11. -I/home/quange/ffmpeg_build/include -L/home/quange/ffmpeg_build/lib -L/usr/local/lib -L/home/quange/ffmpeg_build/lib -lavcodec -lvpx -lm -lpthread -lvpx -lm -lpthread -lvpx -lm -lpthread -lvpx -lm -lpthread -pthread -lm -lz -lfdk-aac -lm -lmp3lame -lm -lopus -lm -lvorbis -lm -logg -lvorbisenc -lvorbis -lm -logg -lx264 -lpthread -lm -ldl -lx265 -lstdc++ -lm -lrt -ldl -lnuma -lswresample -lm -lavutil -pthread -lm -lXv -lX11 -lXext \
12. -I/home/quange/ffmpeg_build/include -L/home/quange/ffmpeg_build/lib -L/usr/local/lib -L/home/quange/ffmpeg_build/lib -lavdevice -lm -lxcb -lXau -lXdmcp -lxcb-shape -lxcb -lXau -lXdmcp -lxcb-xfixes -lxcb-render -lxcb-shape -lxcb -lXau -lXdmcp -lasound -lm -ldl -lpthread -lrt -lSDL2 -Wl,--no-undefined -lm -ldl -lasound -lm -ldl -lpthread -lpulse-simple -lpulse -lsndio -lX11 -lXext -lXcursor -lXinerama -lXi -lXrandr -lXss -lXxf86vm -lwayland-egl -lwayland-client -lwayland-cursor -lxkbcommon -lpthread -lrt -lsndio -lXv -lX11 -lXext -lavfilter -pthread -lm -lfreetype -lz -lpng12 -lz -lm -lswscale -lm -lpostproc -lm -lavformat -lm -lz -lavcodec -lvpx -lm -lpthread -lvpx -lm -lpthread -lvpx -lm -lpthread -lvpx -lm -lpthread -pthread -lm -lz -lfdk-aac -lm -lmp3lame -lm -lopus -lm -lvorbis -lm -logg -lvorbisenc -lvorbis -lm -logg -lx264 -lpthread -lm -ldl -lx265 -lstdc++ -lm -lrt -ldl -lnuma -lswresample -lm -lavutil -pthread -lm -lXv -lX11 -lXext \
13. -I/home/quange/ffmpeg_build/include -L/home/quange/ffmpeg_build/lib -L/usr/local/lib -L/home/quange/ffmpeg_build/lib -lavformat -lm -lz -lavcodec -lvpx -lm -lpthread -lvpx -lm -lpthread -lvpx -lm -lpthread -lvpx -lm -lpthread -pthread -lm -lz -lfdk-aac -lm -lmp3lame -lm -lopus -lm -lvorbis -lm -logg -lvorbisenc -lvorbis -lm -logg -lx264 -lpthread -lm -ldl -lx265 -lstdc++ -lm -lrt -ldl -lnuma -lswresample -lm -lavutil -pthread -lm -lXv -lX11 -lXext \
14. -I/home/quange/ffmpeg_build/include -L/home/quange/ffmpeg_build/lib -L/usr/local/lib -L/home/quange/ffmpeg_build/lib -lavformat -lm -lz -lavcodec -lvpx -lm -lpthread -lvpx -lm -lpthread -lvpx -lm -lpthread -lvpx -lm -lpthread -pthread -lm -lz -lfdk-aac -lm -lmp3lame -lm -lopus -lm -lvorbis -lm -logg -lvorbisenc -lvorbis -lm -logg -lx264 -lpthread -lm -ldl -lx265 -lstdc++ -lm -lrt -ldl -lnuma -lswresample -lm -lavutil -pthread -lm -lXv -lX11 -lXext \
15. -I/home/quange/ffmpeg_build/include -L/home/quange/ffmpeg_build/lib -lswscale -lm -lavutil -pthread -lm -lXv -lX11 -lXext \
16.
17.
18.
19.
20.
21. NAME=$(wildcard *.c)
22. TARGET=av_record
23.
24. $(TARGET):$(OBJS)
25.
26. $(CC) $(FLAG) -o [email protected] $^ $(LIB)
27.
28.
29. %.o:%.c
30. $(CC) $(FLAG) -c -o [email protected] $^ $(LIB)
31.
32.
33.
34. clean:
35. rm -rf $(TARGET) $(OBJS)
3.2結果
使用VLC打開test.flv,可以看到錄制的實時音視訊,音視訊延時維持在200ms以内,更精确的有待測試優化。
3.3存在的問題
無
3.6 思考
1、問:為啥不講音視訊資料的采集,編碼,封裝放在同一個程序裡?
答:
1)、由于音視訊的編碼耗時比較久(特别是視訊),所有操作都放在同一個程序裡面,會影響到資料的采集,造成音頻,視訊資料采集丢失。
2)、由于音視訊在寫入封裝時,需要比較音頻與視訊的pts*time_base,若視訊的實時時間小于音頻,則擷取音頻寫入封裝頻,否則擷取視訊寫入封裝;這樣的判斷方法,或影響到音頻和視訊的采集,造成大部分資料丢失。
2、關于裝置前端采集資料速度思考:
一開始,我是将前端資料采集,擷取視訊幀frame,視訊幀編碼成pkt等三個步驟放在采集線程裡,然後再發送資料到隊列,主程序再擷取隊列中的pkt,直接寫入封裝。但是,考慮到視訊編碼耗時較久,會影響到資料采集,是以還是直接單線程運作擷取資料,最大限度保證資料的采集。
3、關于不同封裝,編碼資料的pts問題
這方面的相關在《ffmpeg-攝像頭采集編碼封裝》中有詳細的講解。總的來說pts*time_base的值在flv,mp4,ts等容器中,值都是一樣的。不同的表現在編碼生成的pts與編碼器時基(1/frame_rate),流時基有關。
4、關于攝像頭實時采集幀率與編碼器設定的幀率關系?
在本例中,攝像頭采集的幀率為20幀,而由于将編碼器時基設為(1/25),就會錄制下來的視訊要比實際上跑的快(ps:10s的視訊從采集手機螢幕5s開始,播放結束後,顯示20s)。主要是因為錄制下來每幀間隔為(1/25),而實際攝像頭是(1/20)。同樣一秒的資料,寫到封裝裡隻有0.8s的資料。是以10s錄制的視訊,顯示的是15s的時間。這問題,編碼器時基改為實際幀率即可。
指令ffmpeg -f video4linux2 -s 640*480 -i /dev/video0 -f flv test.flv可以顯示實時幀率
4.附件
無
5.參考資料
[1] ffmpeg之PCM轉AAC
https://blog.csdn.net/mengzhengjie/article/details/78919067
[2]官方Encode pcm file to aac
http://ffmpeg.org/pipermail/ffmpeg-user/2014-January/019268.html
[3]PCM編碼AAC,參考其普通PCM格式與AAC轉格式差異 https://blog.csdn.net/mengzhengjie/article/details/78919067
[4]https://cloud.tencent.com/developer/article/1194003