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. 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. //check ouput file
15. char push_stream = 0;
16. char *ofmt_name = NULL;
17. if (strstr(filename, "rtmp://") != NULL)
18. {
19. push_stream = 1;
20. ofmt_name = "flv";
21. }
22. else if (strstr(filename, "udp://") != NULL)
23. {
24. push_stream = 1;
25. ofmt_name = "mpegts";
26. }
27. else
28. {
29. push_stream = 0;
30. ofmt_name = NULL;
31. }
32.
33. /* allocate the output media context */
34. avformat_alloc_output_context2(&oc, NULL, ofmt_name, filename);
35. if (!oc) {
36. printf("Could not deduce output format from file extension: using MPEG.\n");
37. avformat_alloc_output_context2(&oc, NULL, "mpeg", filename);
38. }
39. if (!oc)
40. return 1;
41.
42. fmt = oc->oformat;
43.
44. /* Add the audio and video streams using the default format codecs
45. * and initialize the codecs. */
46. if (fmt->video_codec != AV_CODEC_ID_NONE) {
47. add_stream(&video_st, oc, &video_codec, fmt->video_codec);
48. have_video = 1;
49. encode_video = 1;
50. }
51. if (fmt->audio_codec != AV_CODEC_ID_NONE) {
52. add_stream(&audio_st, oc, &audio_codec, AV_CODEC_ID_AAC);//fmt->audio_codec);
53. have_audio = 1;
54. encode_audio = 1;
55. }
56.
57. /* Now that all the parameters are set, we can open the audio and
58. * video codecs and allocate the necessary encode buffers. */
59. if (have_video)
60. open_video(oc, video_codec, &video_st, opt);
61.
62. if (have_audio)
63. open_audio(oc, audio_codec, &audio_st, opt);
64.
65. av_dump_format(oc, 0, filename, 1);
66.
67. /* open the output file, if needed */
68. if (!(fmt->flags & AVFMT_NOFILE)) {
69. ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
70. if (ret < 0) {
71. fprintf(stderr, "Could not open '%s': %s\n", filename,
72. av_err2str(ret));
73. return 1;
74. }
75. }
76.
77. /* Write the stream header, if any. */
78. ret = avformat_write_header(oc, &opt);
79. if (ret < 0) {
80. fprintf(stderr, "Error occurred when opening output file: %s\n",
81. av_err2str(ret));
82. return 1;
83. }
84.
85.
86. output_dev.encode_audio=encode_audio;
87. output_dev.encode_video=encode_video;
88. output_dev.oc=oc;
89. output_dev.have_audio=have_audio;
90. output_dev.have_video=have_video;
91. output_dev.video_st=&video_st;
92. output_dev.audio_st=&audio_st;
93.
94. }
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 $@ $^ $(LIB)
27.
28.
29. %.o:%.c
30. $(CC) $(FLAG) -c -o $@ $^ $(LIB)
31.
32.
33.
34. clean:
35. rm -rf $(TARGET) $(OBJS)
3.2结果
使用软件打开test.flv,可以看到录制的实时音视频,音视频延时维持在200ms以内,更精确的有待测试优化。
./av_record rtmp://127.0.0.1:1935/live/test1
![](/qrcode.jpg)
3.3存在的问题
1、测试发现,音视频在录制过程中,会出现音频超前几秒,同步还待优化。
猜测导致的原因是:本程序采用的是固定帧率计算,手动生成frame的pts,但是实际上会出现视频帧率不稳定,音频采样率也不稳定,这可能会导致音频生成的ptstime_base比视频生成的ptstime_base快的越来越多(即同个时刻生成的音频ptstime_base比视频快ptstime_base)。
优化的方法:在获取到音视频数据是,实时获取当前的时间戳,并记录写入。在封装时,比较写入的时间戳,读取pkt数据,解码,编码,写入封装。
经过测试:测试了音频实时效果,你实时效果是可以的,但是视频录制效果出了问题。在录制输出文件,视频的编码器帧率设为了20帧,同样条件,使用ffmpeg指令ffmpeg -f video4linux2 -r 20 -s 640480 -i /dev/video0 -f flv ffmpeg_test.flv,指定20帧率进行录制时,也会出现录制了50s的文件实际上是过来1:02分钟。但是不指定帧率录制,则正常ffmpeg -f video4linux2 -s 640480 -i /dev/video0 -f flv ffmpeg_test.flv。所以问题还是出现在输出文件的帧率上。
https://blog.csdn.net/zhoubotong2012/article/details/79338093
参考该链接,有相关内容。
3.6 思考
无
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