Move pixel format conversion from uVideoRec to AVWrapper
authorkoda
Sun, 20 Mar 2016 03:08:51 -0400
changeset 11617 b7d5d75469ee
parent 11616 b53c3134d55a
child 11618 66880a42c2d7
Move pixel format conversion from uVideoRec to AVWrapper This has several benefits, being in C-land allows us to better use libav API and avoid mixing memory allocated from Pascal. Also the C code for the conversion loop generated by GCC or Clang is probably more optimized than by Freepascal. Finally it will simplify code in the future if we are going to enable any other pixel format than yuv420p. Change the coefficients to improve color accuracy during conversion.
hedgewars/avwrapper/avwrapper.c
hedgewars/uVideoRec.pas
--- a/hedgewars/avwrapper/avwrapper.c	Sun Mar 20 01:16:11 2016 -0400
+++ b/hedgewars/avwrapper/avwrapper.c	Sun Mar 20 03:08:51 2016 -0400
@@ -333,15 +333,13 @@
     g_pVFrame = av_frame_alloc();
     if (!g_pVFrame)
         return FatalError("Could not allocate frame");
+    av_frame_unref(g_pVFrame);
 
     g_pVFrame->width = g_Width;
     g_pVFrame->height = g_Height;
     g_pVFrame->format = AV_PIX_FMT_YUV420P;
-    g_pVFrame->linesize[0] = g_Width;
-    g_pVFrame->linesize[1] = g_Width/2;
-    g_pVFrame->linesize[2] = g_Width/2;
-    g_pVFrame->linesize[3] = 0;
-    return 0;
+
+    return avcodec_default_get_buffer2(g_pVideo, g_pVFrame, 0);
 }
 
 static int WriteFrame(AVFrame* pFrame)
@@ -418,11 +416,47 @@
     }
 }
 
-AVWRAP_DECL int AVWrapper_WriteFrame(uint8_t* pY, uint8_t* pCb, uint8_t* pCr)
+AVWRAP_DECL int AVWrapper_WriteFrame(uint8_t *buf)
 {
-    g_pVFrame->data[0] = pY;
-    g_pVFrame->data[1] = pCb;
-    g_pVFrame->data[2] = pCr;
+    int x, y, stride = g_Width * 4;
+    uint8_t *data[3];
+
+    // copy pointers, prepare source
+    memcpy(data, g_pVFrame->data, sizeof(data));
+    buf += (g_Height - 1) * stride;
+
+    // convert to YUV 4:2:0
+    for (y = 0; y < g_Height; y++) {
+        for (x = 0; x < g_Width; x++) {
+            int r = buf[x * 4 + 0];
+            int g = buf[x * 4 + 1];
+            int b = buf[x * 4 + 2];
+
+            int luma = (int)(0.299f * r +  0.587f * g + 0.114f * b);
+            data[0][x] = av_clip_uint8(luma);
+
+            if (!(x & 1) && !(y & 1)) {
+                int r = (buf[x * 4 + 0]          + buf[(x + 1) * 4 + 0] +
+                         buf[x * 4 + 0 + stride] + buf[(x + 1) * 4 + 0 + stride]) / 4;
+                int g = (buf[x * 4 + 1]          + buf[(x + 1) * 4 + 1] +
+                         buf[x * 4 + 1 + stride] + buf[(x + 1) * 4 + 1 + stride]) / 4;
+                int b = (buf[x * 4 + 2]          + buf[(x + 1) * 4 + 2] +
+                         buf[x * 4 + 2 + stride] + buf[(x + 1) * 4 + 2 + stride]) / 4;
+
+                int cr = (int)(-0.14713f * r - 0.28886f * g + 0.436f   * b);
+                int cb = (int)( 0.615f   * r - 0.51499f * g - 0.10001f * b);
+                data[1][x / 2] = av_clip_uint8(128 + cr);
+                data[2][x / 2] = av_clip_uint8(128 + cb);
+            }
+        }
+        buf += -stride;
+        data[0] += g_pVFrame->linesize[0];
+        if (y & 1) {
+            data[1] += g_pVFrame->linesize[1];
+            data[2] += g_pVFrame->linesize[2];
+        }
+    }
+
     return WriteFrame(g_pVFrame);
 }
 
--- a/hedgewars/uVideoRec.pas	Sun Mar 20 01:16:11 2016 -0400
+++ b/hedgewars/uVideoRec.pas	Sun Mar 20 03:08:51 2016 -0400
@@ -58,7 +58,7 @@
               filename, desc, soundFile, format, vcodec, acodec: PChar;
               width, height, framerateNum, framerateDen, vquality: LongInt): LongInt; cdecl; external AvwrapperLibName;
 function AVWrapper_Close: LongInt; cdecl; external AvwrapperLibName;
-function AVWrapper_WriteFrame( pY, pCb, pCr: PByte ): LongInt; cdecl; external AvwrapperLibName;
+function AVWrapper_WriteFrame(rgb: PByte): LongInt; cdecl; external AvwrapperLibName;
 
 type TFrame = record
                   realTicks: LongWord;
@@ -121,15 +121,6 @@
         true) then exit(false);
 
     numPixels:= cScreenWidth*cScreenHeight;
-    YCbCr_Planes[0]:= GetMem(numPixels);
-    YCbCr_Planes[1]:= GetMem(numPixels div 4);
-    YCbCr_Planes[2]:= GetMem(numPixels div 4);
-
-    if (YCbCr_Planes[0] = nil) or (YCbCr_Planes[1] = nil) or (YCbCr_Planes[2] = nil) then
-    begin
-        AddFileLog('Error: Could not allocate memory for video recording (YCbCr buffer).');
-        exit(false);
-    end;
 
     RGB_Buffer:= GetMem(4*numPixels);
     if RGB_Buffer = nil then
@@ -147,9 +138,6 @@
 procedure StopVideoRecording;
 begin
     AddFileLog('StopVideoRecording');
-    FreeMem(YCbCr_Planes[0], numPixels);
-    FreeMem(YCbCr_Planes[1], numPixels div 4);
-    FreeMem(YCbCr_Planes[2], numPixels div 4);
     FreeMem(RGB_Buffer, 4*numPixels);
     Close(cameraFile);
     if AVWrapper_Close() < 0 then
@@ -159,36 +147,13 @@
     SendIPC(_S'v'); // inform frontend that we finished
 end;
 
-function pixel(x, y, color: LongInt): LongInt;
-begin
-    pixel:= RGB_Buffer[(cScreenHeight-y-1)*cScreenWidth*4 + x*4 + color];
-end;
-
 procedure EncodeFrame;
-var x, y, r, g, b: LongInt;
-    s: shortstring;
+var s: shortstring;
 begin
     // read pixels from OpenGL
     glReadPixels(0, 0, cScreenWidth, cScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, RGB_Buffer);
 
-    // convert to YCbCr 4:2:0 format
-    // Y
-    for y := 0 to cScreenHeight-1 do
-        for x := 0 to cScreenWidth-1 do
-            YCbCr_Planes[0][y*cScreenWidth + x]:= Byte(16 + ((16828*pixel(x,y,0) + 33038*pixel(x,y,1) + 6416*pixel(x,y,2)) shr 16));
-
-    // Cb and Cr
-    for y := 0 to cScreenHeight div 2 - 1 do
-        for x := 0 to cScreenWidth div 2 - 1 do
-        begin
-            r:= pixel(2*x,2*y,0) + pixel(2*x+1,2*y,0) + pixel(2*x,2*y+1,0) + pixel(2*x+1,2*y+1,0);
-            g:= pixel(2*x,2*y,1) + pixel(2*x+1,2*y,1) + pixel(2*x,2*y+1,1) + pixel(2*x+1,2*y+1,1);
-            b:= pixel(2*x,2*y,2) + pixel(2*x+1,2*y,2) + pixel(2*x,2*y+1,2) + pixel(2*x+1,2*y+1,2);
-            YCbCr_Planes[1][y*(cScreenWidth div 2) + x]:= Byte(128 + ((-2428*r - 4768*g + 7196*b) shr 16));
-            YCbCr_Planes[2][y*(cScreenWidth div 2) + x]:= Byte(128 + (( 7196*r - 6026*g - 1170*b) shr 16));
-        end;
-
-    if AVWrapper_WriteFrame(YCbCr_Planes[0], YCbCr_Planes[1], YCbCr_Planes[2]) < 0 then
+    if AVWrapper_WriteFrame(RGB_Buffer) < 0 then
         halt(-1);
 
     // inform frontend that we have encoded new frame