屏幕录制H.264视频,AAC音频,MP4复用,LibRTMP实时直播
2016-09-01 13:42
537 查看
本文转载自:http://lib.csdn.net/article/57/40046?knId=1544
上周完成了屏幕录制的程序,实时抓屏、录音,视频采用H.264压缩,音频采用AAC压缩,复用为MP4格式,这样可以在电脑和ios和安卓的移动设备上直接播放。支持HTML5的播放器都可以放,这是标准格式的好处。抓屏也增加了自动缩放的功能,参考我的上一篇博客。把这几部分的思路都整理一下。
抓屏,方法很多,直接用bitblt、使用directx、使用mirrordriver、甚至还有用mediaencoder的,我比较了bitblt和directx的方法,也查了很多资料。直觉的理解应该是directx的速度会快一些,但是事实上由于win7系统对bitblt进行了处理,速度与directx抓屏不相上下,代码也简洁很多很多,几行代码就解决,在我的thinkpad T410每秒轻轻松松抓屏100多帧,CPU占用也不高。在有些显卡比较差的机器上,性能会急剧下降,但是这种情况下directx也好不到哪去。单纯从抓屏来说,用bitblt足够了。mediaencoder就算了,太麻烦,还得安装一堆东西。mirrordriver没去仔细看,似乎是用这个驱动可以从镜像里面直接获得变化的屏幕数据,这和我的需求不同,我是需要抓整屏,而不是只要变化的部分。如果不是抓屏,而是做屏幕的远程控制,那可以参考vnc、TightVNC、ReallVNC这些开源的代码,都做得相当好,机制不同。我希望是用标准的格式去压缩整个屏幕的,这点VNC的小伙伴们采用的机制无法实现。
抓屏代码:
抓屏以后,RGB32位的数据要转换为YUV,这个代码如果直接用浮点运算实现,那效率相当低。我用的是xvid里面的一段汇编代码,使用了MMX指令,速度很快。大家可以去参考xvid的代码。话说xvid好几年没更新代码,最近又开始更新了,好奇怪。。。以前做mpeg4的时候,感觉xvid太牛了,编码效率极高,现在被x264取代了。。。扯远了,看看RGB2YUV的代码:
;/**************************************************************************
转换为YUV数据以后,就是压缩了。用H.264,基本上都是x264了,开源的代码,现在速度也挺快,交叉编译生成DLL直接调用就行。这里参数设置很重要,帧率我做的是每分钟1帧到25x60帧可调,注意是每分钟不是秒。x264大家都很熟悉,不多说了。
音频部分比较简单了,设定采样率,用acm采集,然后aac压缩。AAC也有开源的LibFAAC代码可以使用。
264文件和AAC文件生成以后,复用的方法有很多,简单起见,可以用ffmpeg或其他的软件进行复用,注意别让ffmpeg进行二次编码压缩,否则速度慢而且质量损失。
完成的程序,测了一下,1920x1980的分辨率,自动缩放为1280x720,每秒1帧,录制10分钟的mp4文件大概是4M多,画面清晰,声音清晰,达到预期目标了。接下来的工作是实时直播了。考虑到兼容性和流媒体的特点,使用rtmp协议,而没有用rtsp。用rtmp的优点就是接收端用flash可以播放,不需要安装其他插件了。
rtmp协议是不开放的,但是也难不倒我们,librtmp是开源的,做的很好。但是移植到windows平台编译还是费了一些周折。要注意的几点:
1、在c/c++预处理器加上NO_CRYPTO,不编ssl部分,不需要加密
2、rtmp.c文件里面,注释掉几行代码
3、编译时需要链接ws2_32.lib库
编译以后,生成dll,在主程序里面调用,基本的初始化代码:
然后在x264_encocer_encode编码一帧以后,调用rtmp的函数进行发送。
发送的代码就不贴了,就是填充rtmp packet的内容。调试的时候发现创建socket失败,折腾半天最后发现是没有调用WSAStartup,低级错误。。。发送的数据流,用Flex air写了个接收程序,也有问题,air3.1可以正常接收,air3.9就不行。好吧。。。今天继续看代码吧,找找原因。音频的发送程序类似,有空再加。
这个程序将会是我们即将发布的轻录播的一部分,这几天把其他代码完成考虑把程序共享出来。欢迎大家拍砖。
上周完成了屏幕录制的程序,实时抓屏、录音,视频采用H.264压缩,音频采用AAC压缩,复用为MP4格式,这样可以在电脑和ios和安卓的移动设备上直接播放。支持HTML5的播放器都可以放,这是标准格式的好处。抓屏也增加了自动缩放的功能,参考我的上一篇博客。把这几部分的思路都整理一下。
抓屏,方法很多,直接用bitblt、使用directx、使用mirrordriver、甚至还有用mediaencoder的,我比较了bitblt和directx的方法,也查了很多资料。直觉的理解应该是directx的速度会快一些,但是事实上由于win7系统对bitblt进行了处理,速度与directx抓屏不相上下,代码也简洁很多很多,几行代码就解决,在我的thinkpad T410每秒轻轻松松抓屏100多帧,CPU占用也不高。在有些显卡比较差的机器上,性能会急剧下降,但是这种情况下directx也好不到哪去。单纯从抓屏来说,用bitblt足够了。mediaencoder就算了,太麻烦,还得安装一堆东西。mirrordriver没去仔细看,似乎是用这个驱动可以从镜像里面直接获得变化的屏幕数据,这和我的需求不同,我是需要抓整屏,而不是只要变化的部分。如果不是抓屏,而是做屏幕的远程控制,那可以参考vnc、TightVNC、ReallVNC这些开源的代码,都做得相当好,机制不同。我希望是用标准的格式去压缩整个屏幕的,这点VNC的小伙伴们采用的机制无法实现。
抓屏代码:
BitBlt(hMemDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY); GetDIBits(hMemDC, bmp, 0, height, bmpBuffer, &bminfo, DIB_RGB_COLORS);
抓屏以后,RGB32位的数据要转换为YUV,这个代码如果直接用浮点运算实现,那效率相当低。我用的是xvid里面的一段汇编代码,使用了MMX指令,速度很快。大家可以去参考xvid的代码。话说xvid好几年没更新代码,最近又开始更新了,好奇怪。。。以前做mpeg4的时候,感觉xvid太牛了,编码效率极高,现在被x264取代了。。。扯远了,看看RGB2YUV的代码:
;/**************************************************************************
; * ; * mmx colorspace conversions ; * ; *************************************************************************/ bits 32 section .data %macro cglobal 1 %ifdef PREFIX global _%1 %define %1 _%1 %else global %1 %endif %endmacro align 16 ;=========================================================================== ; yuv constants ;=========================================================================== %define Y_R 0.257 %define Y_G 0.504 %define Y_B 0.098 %define Y_ADD 16 %define U_R 0.148 %define U_G 0.291 %define U_B 0.439 %define U_ADD 128 %define V_R 0.439 %define V_G 0.368 %define V_B 0.071 %define V_ADD 128 ;=========================================================================== ; multiplication matrices ;=========================================================================== ; %define SCALEBITS 8 y_mul dw 25 ; FIX(Y_B) dw 129 ; FIX(Y_G) dw 66 ; FIX(Y_R) dw 0 u_mul dw 112 ; FIX(U_B) dw -74 ; FIX(U_G) dw -38 ; FIX(U_R) dw 0 v_mul dw -18 ; FIX(V_B) dw -94 ; FIX(V_G) dw 112 ; FIX(V_R) dw 0 section .text ;=========================================================================== ; ; void rgb24_to_yv12_mmx(uint8_t * const y_out, ; uint8_t * const u_out, ; uint8_t * const v_out, ; const uint8_t * const src, ; const uint32_t width, ; const uint32_t height, ; const uint32_t stride) ; ; always flips ; ;=========================================================================== align 16 cglobal rgb24_to_yv12_mmx rgb24_to_yv12_mmx push ebx push ecx push esi push edi push ebp ; STACK BASE = 20 ; global consants mov eax, [esp + 20 + 28] ; stride mov ecx, [esp + 20 + 20] ; width mov ebx, eax sub ebx, ecx shr ebx, 1 ; ebx = (stride-width) / 2; push ebx ; [esp + 20] = uv_dif ; STACK BASE = 24 add eax, eax sub eax, ecx ; eax = 2*stride - width push eax ; [esp + 16] = y_dif ; STACK BASE = 28 mov ebx, ecx ; shr ebx, 1 ; push ebx ; [esp + 12] = width/2 ; STACK BASE = 32 mov edx, ecx add ecx, edx add ecx, edx ; ecx = 3*width (use 4 for rgb32) push ecx ; [esp + 8] = width3 ; STACK BASE = 36 mov edx, ecx add edx, ecx add edx, ecx ; edx = 3*width3 push edx ; [esp + 4] = src_dif ; STACK BASE = 40 mov esi, [esp + 40 + 16] ; src mov ebp, [esp + 40 + 24] ; eax = height mov eax, ebp sub eax, 2 mul ecx add esi, eax ; src += (height-2) * width3 mov edi, [esp + 40 + 4] ; y_out mov ecx, [esp + 40 + 8] ; u_out mov edx, [esp + 40 + 12] ; v_out movq mm7, [y_mul] shr ebp, 1 ; ebp = height / 2 push ebp ; [esp+0] = tmp ; STACK BASE = 44 .yloop mov ebp, [esp + 12] ; ebp = width /2 .xloop ; y_out mov ebx, [esp + 8] ; ebx = width3 pxor mm4, mm4 pxor mm5, mm5 movd mm0, [esi] ; src[0...] movd mm2, [esi+ebx] ; src[width3...] punpcklbw mm0, mm4 ; [ |b |g |r ] punpcklbw mm2, mm5 ; [ |b |g |r ] movq mm6, mm0 ; = [ |b4|g4|r4] paddw mm6, mm2 ; +[ |b4|g4|r4] pmaddwd mm0, mm7 ; *= Y_MUL pmaddwd mm2, mm7 ; *= Y_MUL movq mm4, mm0 ; [r] movq mm5, mm2 ; [r] psrlq mm4, 32 ; +[g] psrlq mm5, 32 ; +[g] paddd mm0, mm4 ; +[b] paddd mm2, mm5 ; +[b] pxor mm4, mm4 pxor mm5, mm5 movd mm1, [esi+3] ; src[4...] movd mm3, [esi+ebx+3] ; src[width3+4...] punpcklbw mm1, mm4 ; [ |b |g |r ] punpcklbw mm3, mm5 ; [ |b |g |r ] paddw mm6, mm1 ; +[ |b4|g4|r4] paddw mm6, mm3 ; +[ |b4|g4|r4] pmaddwd mm1, mm7 ; *= Y_MUL pmaddwd mm3, mm7 ; *= Y_MUL movq mm4, mm1 ; [r] movq mm5, mm3 ; [r] psrlq mm4, 32 ; +[g] psrlq mm5, 32 ; +[g] paddd mm1, mm4 ; +[b] paddd mm3, mm5 ; +[b] mov ebx, [esp + 44 + 28] ; stride movd eax, mm0 shr eax, 8 add eax, Y_ADD mov [edi + ebx], al movd eax, mm1 shr eax, 8 add eax, Y_ADD mov [edi + ebx + 1], al movd eax, mm2 shr eax, 8 add eax, Y_ADD mov [edi], al movd eax, mm3 shr eax, 8 add eax, Y_ADD mov [edi + 1], al ; u_out, v_out movq mm0, mm6 ; = [ |b4|g4|r4] pmaddwd mm6, [v_mul] ; *= V_MUL pmaddwd mm0, [u_mul] ; *= U_MUL movq mm1, mm0 movq mm2, mm6 psrlq mm1, 32 psrlq mm2, 32 paddd mm0, mm1 paddd mm2, mm6 movd eax, mm0 shr eax, 10 add eax, U_ADD mov [ecx], al movd eax, mm2 shr eax, 10 add eax, V_ADD mov [edx], al add esi, 2 * 3 ; (use 4 for rgb32) add edi, 2 inc ecx inc edx dec ebp jnz near .xloop sub esi, [esp + 4] ; src -= src_dif add edi, [esp + 16] ; y_out += y_dif add ecx, [esp + 20] ; u_out += uv_dif add edx, [esp + 20] ; v_out += uv_dif dec dword [esp+0] jnz near .yloop emms add esp, 24 pop ebp pop edi pop esi pop ecx pop ebx ret ;=========================================================================== ; ; void rgb32_to_yv12mmx(uint8_t * const y_out, ; uint8_t * const u_out, ; uint8_t * const v_out, ; const uint8_t * const src, ; const uint32_t width, ; const uint32_t height, ; const uint32_t stride) ; ; always flips ; ;=========================================================================== align 16 cglobal rgb32_to_yv12_mmx rgb32_to_yv12_mmx push ebx push ecx push esi push edi push ebp ; STACK BASE = 20 ; global consants mov eax, [esp + 20 + 28] ; stride mov ecx, [esp + 20 + 20] ; width mov ebx, eax sub ebx, ecx shr ebx, 1 ; ebx = (stride-width) / 2; push ebx ; [esp + 20] = uv_dif ; STACK BASE = 24 add eax, eax sub eax, ecx ; eax = 2*stride - width push eax ; [esp + 16] = y_dif ; STACK BASE = 28 mov ebx, ecx ; shr ebx, 1 ; push ebx ; [esp + 12] = width/2 ; STACK BASE = 32 mov edx, ecx shl ecx, 2 ; ecx = 4*width (use 4 for rgb32) push ecx ; [esp + 8] = width4 ; STACK BASE = 36 mov edx, ecx add edx, ecx add edx, ecx ; edx = 3*width4 push edx ; [esp + 4] = src_dif ; STACK BASE = 40 mov esi, [esp + 40 + 16] ; src mov ebp, [esp + 40 + 24] ; eax = height mov eax, ebp sub eax, 2 mul ecx add esi, eax ; src += (height-2) * width4 mov edi, [esp + 40 + 4] ; y_out mov ecx, [esp + 40 + 8] ; u_out mov edx, [esp + 40 + 12] ; v_out movq mm7, [y_mul] shr ebp, 1 ; ebp = height / 2 push ebp ; [esp+0] = tmp ; STACK BASE = 44 .yloop mov ebp, [esp + 12] ; ebp = width /2 .xloop ; y_out mov ebx, [esp + 8] ; ebx = width4 pxor mm4, mm4 movq mm0, [esi] ; src[4... |0... ] movq mm2, [esi+ebx] ; src[width4+4...|width4...] movq mm1, mm0 movq mm3, mm2 punpcklbw mm0, mm4 ; [ |b |g |r ] punpcklbw mm2, mm4 ; [ |b |g |r ] punpckhbw mm1, mm4 ; [ |b |g |r ] punpckhbw mm3, mm4 ; [ |b |g |r ] movq mm6, mm0 ; = [ |b4|g4|r4] paddw mm6, mm2 ; +[ |b4|g4|r4] pmaddwd mm0, mm7 ; *= Y_MUL pmaddwd mm2, mm7 ; *= Y_MUL movq mm4, mm0 ; [r] movq mm5, mm2 ; [r] psrlq mm4, 32 ; +[g] psrlq mm5, 32 ; +[g] paddd mm0, mm4 ; +[b] paddd mm2, mm5 ; +[b] paddw mm6, mm1 ; +[ |b4|g4|r4] paddw mm6, mm3 ; +[ |b4|g4|r4] pmaddwd mm1, mm7 ; *= Y_MUL pmaddwd mm3, mm7 ; *= Y_MUL movq mm4, mm1 ; [r] movq mm5, mm3 ; [r] psrlq mm4, 32 ; +[g] psrlq mm5, 32 ; +[g] paddd mm1, mm4 ; +[b] paddd mm3, mm5 ; +[b] mov ebx, [esp + 44 + 28] ; stride movd eax, mm0 shr eax, 8 add eax, Y_ADD mov [edi + ebx], al movd eax, mm1 shr eax, 8 add eax, Y_ADD mov [edi + ebx + 1], al movd eax, mm2 shr eax, 8 add eax, Y_ADD mov [edi], al movd eax, mm3 shr eax, 8 add eax, Y_ADD mov [edi + 1], al ; u_out, v_out movq mm0, mm6 ; = [ |b4|g4|r4] pmaddwd mm6, [v_mul] ; *= V_MUL pmaddwd mm0, [u_mul] ; *= U_MUL movq mm1, mm0 movq mm2, mm6 psrlq mm1, 32 psrlq mm2, 32 paddd mm0, mm1 paddd mm2, mm6 movd eax, mm0 shr eax, 10 add eax, U_ADD mov [ecx], al movd eax, mm2 shr eax, 10 add eax, V_ADD mov [edx], al add esi, 2 * 4 ; (use 4 for rgb32) add edi, 2 inc ecx inc edx dec ebp jnz near .xloop sub esi, [esp + 4] ; src -= src_dif add edi, [esp + 16] ; y_out += y_dif add ecx, [esp + 20] ; u_out += uv_dif add edx, [esp + 20] ; v_out += uv_dif dec dword [esp+0] jnz near .yloop emms add esp, 24 pop ebp pop edi pop esi pop ecx pop ebx ret
转换为YUV数据以后,就是压缩了。用H.264,基本上都是x264了,开源的代码,现在速度也挺快,交叉编译生成DLL直接调用就行。这里参数设置很重要,帧率我做的是每分钟1帧到25x60帧可调,注意是每分钟不是秒。x264大家都很熟悉,不多说了。
音频部分比较简单了,设定采样率,用acm采集,然后aac压缩。AAC也有开源的LibFAAC代码可以使用。
264文件和AAC文件生成以后,复用的方法有很多,简单起见,可以用ffmpeg或其他的软件进行复用,注意别让ffmpeg进行二次编码压缩,否则速度慢而且质量损失。
完成的程序,测了一下,1920x1980的分辨率,自动缩放为1280x720,每秒1帧,录制10分钟的mp4文件大概是4M多,画面清晰,声音清晰,达到预期目标了。接下来的工作是实时直播了。考虑到兼容性和流媒体的特点,使用rtmp协议,而没有用rtsp。用rtmp的优点就是接收端用flash可以播放,不需要安装其他插件了。
rtmp协议是不开放的,但是也难不倒我们,librtmp是开源的,做的很好。但是移植到windows平台编译还是费了一些周折。要注意的几点:
1、在c/c++预处理器加上NO_CRYPTO,不编ssl部分,不需要加密
2、rtmp.c文件里面,注释掉几行代码
//#ifdef _DEBUG //extern FILE *netstackdump; //extern FILE *netstackdump_read; //#endif //#ifdef _DEBUG // fwrite(ptr, 1, nBytes, netstackdump_read); //#endif //#ifdef _DEBUG // fwrite(buf, 1, len, netstackdump); //#endif
3、编译时需要链接ws2_32.lib库
编译以后,生成dll,在主程序里面调用,基本的初始化代码:
/*分配与初始化*/ rtmp = RTMP_Alloc(); RTMP_Init(rtmp); /*设置URL*/ if (RTMP_SetupURL(rtmp,rtmp_url) == FALSE) { log(LOG_ERR,"RTMP_SetupURL() failed!"); RTMP_Free(rtmp); return -1; } /*设置可写,即发布流,这个函数必须在连接前使用,否则无效*/ RTMP_EnableWrite(rtmp); /*连接服务器*/ if (RTMP_Connect(rtmp, NULL) == FALSE) { log(LOG_ERR,"RTMP_Connect() failed!"); RTMP_Free(rtmp); return -1; } /*连接流*/ if (RTMP_ConnectStream(rtmp,0) == FALSE) { log(LOG_ERR,"RTMP_ConnectStream() failed!"); RTMP_Close(rtmp); RTMP_Free(rtmp); return -1; }
然后在x264_encocer_encode编码一帧以后,调用rtmp的函数进行发送。
size = x264_encoder_encode(cx->hd,&nal,&n,pic,&pout); int i,last; for (i = 0,last = 0;i < n;i++) { if (nal[i].i_type == NAL_SPS) { sps_len = nal[i].i_payload-4; memcpy(sps,nal[i].p_payload+4,sps_len); } else if (nal[i].i_type == NAL_PPS) { pps_len = nal[i].i_payload-4; memcpy(pps,nal[i].p_payload+4,pps_len); /*发送sps pps*/ send_video_sps_pps(); } else { /*发送普通帧(剩下全部)*/ send_rtmp_video(nal[i].p_payload,size-last) break; } last += nal[i].i_payload; }
发送的代码就不贴了,就是填充rtmp packet的内容。调试的时候发现创建socket失败,折腾半天最后发现是没有调用WSAStartup,低级错误。。。发送的数据流,用Flex air写了个接收程序,也有问题,air3.1可以正常接收,air3.9就不行。好吧。。。今天继续看代码吧,找找原因。音频的发送程序类似,有空再加。
这个程序将会是我们即将发布的轻录播的一部分,这几天把其他代码完成考虑把程序共享出来。欢迎大家拍砖。
相关文章推荐
- 屏幕录制H.264视频,AAC音频,MP4复用,LibRTMP实时直播
- 屏幕录制H.264视频,AAC音频,MP4复用,LibRTMP实时直播
- 屏幕录制H.264视频,AAC音频,MP4复,LibRTMP现场活动
- iOS RTMP 视频直播开发笔记(5)- iOS H.264 实时硬编码实现原理
- RTMP协议发送H.264编码及AAC编码的音视频,实现摄像头直播
- 实时视频在网页直播--windows下编译librtmp、rtmpdump
- iOS 简单的视频直播功能开发(实时视音频流录制编码+RTMP传输+实时拉流解码播放)
- (转)C++实现RTMP协议发送H.264编码及AAC编码的音视频,摄像头直播
- iOS RTMP 视频直播开发笔记(2)----关于MP4及H.264
- C++实现RTMP协议发送H.264编码及AAC编码的音视频,摄像头直播
- iOS 简单的视频直播功能开发(实时视音频流录制编码+RTMP传输+实时拉流解码播放)
- 发布一个软件,轻新视频录播程序,H264/AAC录制视音频,保存FLV,支持RTMP直播
- RTMP协议发送H.264编码及AAC编码的音视频,实现摄像头直播
- 公布一个软件,轻新视频录播程序,H264/AAC录制视音频,保存FLV,支持RTMP直播
- RTMP协议发送H.264编码及AAC编码的音视频,实现摄像头直播
- iOS RTMP 视频直播开发笔记(5)------- iOS H.264 实时硬编码实现原理
- 实时视频在网页直播--windows下编译librtmp、rtmpdump
- C++实现RTMP协议发送H.264编码及AAC编码的音视频,摄像头直播
- iOS RTMP 视频直播开发笔记(5)------- iOS H.264 实时硬编码实现原理
- C++实现RTMP协议发送H.264编码及AAC编码的音视频,摄像头直播