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-摄像头采集编码封装》,发现录制的单纯摄像头视频也是不同步的(即录制的时间与现实的时间不一致),即使是修改了封装文件的输出帧率,较贴近摄像头的采集帧率,也会偶尔不同步。
基于上述的问题,重新使用ffmpeg命令来录制视频,发现没有固定帧率录制的视频基本上是同步的。
ffmpeg -f video4linux2 -s 640*480 -i /dev/video0 -debug_ts -f flv test.flv
所以参考ffmpeg.c源码,提取其时间戳写入方法,重新修改了程序音视频同步方式:基于首帧获取的音频/视频时间戳,往后获取的cur_pts=pkt.pts-first.pts,获取cur_pts实时时间,再比较音视频的pts,来写入封装。这样子就基本上不会出错了,而且音视频同步基本上可以已达到200ms以内。
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 || frame_pts<=frame_audio_pts)) //比较音频视频产生是的实时时间戳大小,以音频frame_audio_pts为基准,若视频的frame_pts小于音频,则写入视频帧,否则写入音频帧
4. {
5. if(packet_queue_get(&output_dev.videoq,&pkt,0,&frame_pts)<0) //获取队列中的视频pkt
6. {
7. printf("packet_queue_get Error.\n");
8. break;
9. }
10.
11. if(flush_pkt.data== pkt.data)
12. {
13. printf("get pkt flush_pkt\n");
14. continue;
15. }
16.
17.
18. vframe=get_video_pkt2Frame(output_dev.video_st,&video_input,&pkt,&got_pic,frame_pts); //解码pkt 成frame
19. if(!got_pic)
20. {
21. av_free_packet(&pkt);
22. printf("get_video_pkt2Frame error\n");
23. usleep(10000);
24. continue;
25. }
26. av_free_packet(&pkt);
27.
28. WRITE_FRAME:
29. output_dev.encode_video = !write_video_frame1(output_dev.oc, output_dev.video_st,vframe); //编码frame成pkt,并且写入封装
30. //usleep(300000);
31. }
32. else//audio
33. {if(packet_queue_get(&output_dev.audioq,&audio_pkt,0,&frame_audio_pts)<0) //获取队列中的音频pkt
34.
35. {
36. printf("packet_queue_get Error.\n");
37. break;
38. }
39.
40. if(flush_pkt.data== audio_pkt.data)
41. {
42. printf("get pkt flush_pkt\n");
43. continue;
44. }
45. //av_free_packet(&audio_pkt);
46.
47. #if 1
48.
49.
50. aframe=get_audio_pkt2Frame(output_dev.audio_st,&alsa_input,&audio_pkt,&got_pcm,frame_audio_pts); //解码pkt 成frame
51. if(!got_pcm)
52. {
53. av_free_packet(&audio_pkt);
54. printf("get_video_pkt2Frame error\n");
55. usleep(10000);
56. continue;
57. }
58. av_free_packet(&audio_pkt);
59.
60. WRITE_AUDIO_FRAME:
61. output_dev.encode_audio = !write_audio_frame1(output_dev.oc, output_dev.audio_st,aframe); //编码frame成pkt,并且写入封装
62.
63. //usleep(300000);
64. #endif
65. }
66.
67.
68.
69.
70. }
2.7改进部分
1. if(av_read_frame(input->a_ifmtCtx, input->in_packet)>=0){//pkt数据获取
2. if(input->in_packet->stream_index==input->audioindex){
3.
4. if(input->ts_offset_flg==0)//保存第一帧时间戳
5. {
6. input->ts_offset_flg=1;
7. input->ts_offset=-input->in_packet->pts;
8. }
9. //获取相对参考的时间戳
10. if (input->in_packet->dts != AV_NOPTS_VALUE)
11. input->in_packet->dts += av_rescale_q(input->ts_offset, AV_TIME_BASE_Q,in_stream->time_base);
12. if (input->in_packet->pts != AV_NOPTS_VALUE)
13. input->in_packet->pts += av_rescale_q(input->ts_offset, AV_TIME_BASE_Q, in_stream->time_base);
14. //计算采集的数据是否是连续的,不连续则需要调整时间戳以及参考时间戳
15. pkt_dts = av_rescale_q_rnd(input->in_packet->dts, in_stream->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
16. if ( pkt_dts != AV_NOPTS_VALUE && input->last_ts != AV_NOPTS_VALUE) {
17. int64_t delta = pkt_dts - input->last_ts;
18.
19. if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE ||
20. delta > 1LL*dts_delta_threshold*AV_TIME_BASE){
21. input->ts_offset -= delta;
22. av_log(NULL, AV_LOG_DEBUG,
23. "Inter stream timestamp discontinuity %"PRId64", new offset= %"PRId64"\n",
24. delta, input->ts_offset);
25. input->in_packet->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, in_stream->time_base);
26. if (input->in_packet->pts != AV_NOPTS_VALUE)
27. input->in_packet->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, in_stream->time_base);
28. }
29. }
30.
31. if(input->in_packet->pts!=0)
32. {
33. if (av_compare_ts(input->in_packet->pts, in_stream->time_base,
34. STREAM_DURATION, (AVRational){ 1, 1 }) >= 0)
35. {
36. av_free_packet(input->in_packet);
37. return NULL;
38. }
39. }
40. //记录保存最新的时间戳
41.
42. if (input->in_packet->dts != AV_NOPTS_VALUE)
43. input->last_ts = av_rescale_q(input->in_packet->dts, in_stream->time_base, AV_TIME_BASE_Q);
44.
45.
46. ret_pkt=input->in_packet;
47. ost->next_pts = input->in_packet->pts;
48. }
49. //av_free_packet(input->in_packet);
50. }
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
录制功能:./av_record test.flv
3.3存在的问题
- 使用流命令推流./ffmpeg -f video4linux2 -s 640x480 -i /dev/video0 -f flv rtmp://127.0.0.1:1935/live/live也会出现这个
1. [flv @ 0xb9aef80] Failed to update header with correct duration.
2. [flv @ 0xb9aef80] Failed to update header with correct filesize.
-
使使用程序音视频同步推流时,在客户端会出现音视频偶尔卡顿问题,单独音频或者是视频推流时,则正常。视频音视频同步录制也正常。
猜测1:可能是视频在判断与音频时间戳时,会相对单独视频发送时,会延时发送,所以导致客户端播放没有数据,导致延时
猜测2:可能是视频输出的帧率设置为30,而实际上的帧率只有20帧左右,导致数据发送量跟不上,另外一个原因是,在数据发送的时候,没有采用固定的帧率对应的时间进行发送。参考收流与推流文档,对于流媒体,只需要按照视频1/framerate间隔时间来发送,音频数据直接发送即可。
3.6 思考
无
4.附件
指令录制视频 ffmpeg -f video4linux2 -s 640*480 -i /dev/video0 -f flv test.flv
指令录制音频ffmpeg -f alsa -ar 44100 -i default ffmpeg_record_audio.wav
指令录制音视频ffmpeg -f video4linux2 -s 640*480 -i /dev/video0 -f alsa -ar 44100 -i default -f flv audio_video.mp4
指令分离音视频:ffmpeg -i test.mp4 -vn -y -acodec copy test.aac
ffmpeg -i test.mp4 -vn -y -avcodec copy test.h264
指令查看录制的视频文件PTS信息 ffprobe -show_frames -select_streams v test.flv | grep pkt_pts | head
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
[5] FFmpeg流媒体处理-收流与推流
https://www.cnblogs.com/leisure_chn/p/10623968.html
[6]FFmpeg 编解码处理1-转码全流程简介
https://www.cnblogs.com/leisure_chn/p/10584901.html