屏幕录制H.264视频,AAC音频,MP4复用,LibRTMP实时直播

    上周完成了屏幕录制的程序,实时抓屏、录音,视频采用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就不行。好吧。。。今天继续看代码吧,找找原因。音频的发送程序类似,有空再加。

    这个程序将会是我们即将发布的轻录播的一部分,这几天把其他代码完成考虑把程序共享出来。欢迎大家拍砖。


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值