Sunday, October 7, 2012

Raspberry Pi Setup

After loading the Raspberry Pi image, the device boots into the raspi-config tool. But since the Pi is powered off a USB port off my desk (not recommended by the manual, presumably because the USB port may or may not supply enough current), I'd like to do some additional setup to facilitate my development process: 

1. Wifi setup

Wifi setup was fairly straight forward with a compatible Wifi dongle. I bought the Asus N10 from a local retailer and followed the instruction here: http://www.raspberrypi-tutorials.co.uk/set-raspberry-pi-wireless-network/

More specifically, all I had to do was: 

sudo vi /etc/wpa.config

And add: 

network={
ssid="MY_SSID"
proto=RSN
key_mgmt=WPA-PSK
pairwise=CCMP TKIP
group=CCMP TKIP
psk="WPA-PASSWORD"
}

Then: 

sudo vi /etc/network/interfaces

And add: 

auto wlan0
iface wlan0 inet dhcp
wpa-conf /etc/wpa.config

And restart the network interface: 

sudo /etc/init.d/networking restart

Now I can ssh into the Pi using Putty. 

Edit: 
I tried to set this up with using a keyboard connected, the Pi had problem reading the conf file and led to wpa_supplicant failure, even though the dongle was detected correctly (ifconfig shows wlan0). 

I think this is relating to the default keyboard layout of the Pi and the fact I'm using an international keyboard. 

I connect the Pi locally using an ethernet cable, ssh'ed into the Pi and re-wrote the config file and issued ifdown and ifup to wlan0. The interface came up correctly. 

2. Disable overscan 

When my Pi boots up there's a black margin around the console. I'm not sure why this happens, but all I had to do was to disable overscan: 

sudo vi /boot/config.txt

Uncomment the following line: 

disable_overscan=1

Power cycle the Pi. 

3. Extend the partition

By default the image has a partition of < 60M. To extend that: 

sudo raspi-config

And select: 

expand_rootfs

Wait and reboot. Note that the resizing on the next boot will take a bit of time. My 16GB took about 15 minutes or more. 

4. Mount the Windows share drive: http://www.swerdna.net.au/susesambacifs.html

mount -t cifs -o username=guest,password=guest //192.168.1.100/share /mount/share

5. Install pv (file copy with progress indicator): http://www.commandlinefu.com/commands/view/5107/copy-a-file-using-pv-and-watch-its-progress

sudo apt-get install pv
pv source > destination



Free Private Online Repository

I recently discovered a free online code hosting service: Bitbucket (https://bitbucket.org/). 

Most other services I looked at previously forces the code to be open sourced if not paid. The biggest bonus for Bitbucket is that it provides free private online repository service (but only with up to a limited developers per project). 

However, for my purpose (private projects on the sides and such), it's sufficient. 

Raspberry Pi File Sharing

To perform file sharing with Raspberry Pi, I had to install the Samba server. General instruction can be found here: 
http://ubuntuforums.org/showthread.php?t=202605

I had to add a section of my smb.conf file at the end: 
sudo vi /etc/samba/smb.conf

[Share]
comment = guest access share
path = /home/pi/downloads
browseable = yes
read only = yes
guest ok = yes

And restart the Samba server. 

Now everyone can access my /home/pi/downloads directory. 

Capturing Frame Buffer

On the Raspberry Pi the default the framebuffer depth seems to defaults to 16bit (I don't know if this true with other Linux distribution). I want to capture frame buffer at 24bits per pixel:

Set the frame buffer to the right depth:
fbset -depth 24

Capture the frame
cat /dev/fb0 > screenshot.raw

For some reason the captured frame provides more data at the end. With 24 bits (3 bytes) per pixel the actual expected frame size is 1920x1080x3 = 6220800. I was able to just truncate the file and drop the rest:
truncate --size 6220800 screenshot.raw

To convert this back to a readable png format:
ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt rgb24 -s 1920x1080 -i screenshot.raw -f imag2 -vcodec png screenshot.png

SSH Auto Login with Putty

One of my Raspberry Pis is powered on 24/7. To access it I always SSH into it using Putty. It took a couple weeks beforeI got fed with entering my username and password every time I want to work on it. 

I did a quick Google search and came to this: 

http://superuser.com/questions/44106/is-there-a-way-to-auto-login-in-putty-with-a-password

Which essentially come to creating a shortcut to putty.exe, and add the following argument: 

pi@192.168.1.151 -pw raspberry

Now I can access my Pi by simply double clicking on the shortcut. 

Saturday, October 6, 2012

Raspberry Pi as a hardware assisted H264 encoder

One of the amazing things about the Pi is despite its limited CPU power, it has an amazing GPU. This can observed from the fact: 

  • It can do high profile h264 encode/decode (1080p @ 30 fps)
  • It can play Quake 3
  • It can run fancy Qt demos 
Just recently the default Raspberry Pi firmware now has the h264 encoder feature. kulve from the Raspberry Pi forum gave a step by step instruction in the following post: 


I've captured the code here in case the link expires someday: 

# Get The Hobbit in 640 x 272 H264
youtube-dl -f 18 "http://www.youtube.com/watch?v=SDnYMbYB-nU"
 
# Convert 9 seconds of frames to PNG
mplayer -vo png -nosound -ss 61 -endpos 9  SDnYMbYB-nU.mp4
 
# Convert PNG frames to raw RGB (don't ming the weird .png.raw naming)
for i in *png; do convert $i rgb:$i.raw; done

hello_video.bin testout.h264

ffmpeg -f h264 -i testout.h264 -vcodec copy outtest.mp4

mplayer outtest.mp4

I had to install a couple utilities, including youtube-dl, convert, mplayer and ffmpeg. The youtube-dl from the repository seemed to be obsolete already, and I had to download a new one from the official source. 

The hello_video.bin is written by kulve from the Raspberry Pi forum, with the code listed below: 
/*
Copyright (c) 2012, Broadcom Europe Ltd
Copyright (c) 2012, Kalle Vahlman 
                    Tuomas Kulve 
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// Video deocode demo using OpenMAX IL though the ilcient helper library

#include 
#include 
#include 

#include "bcm_host.h"
#include "ilclient.h"

#define RAW_WIDTH    640
#define RAW_HEIGHT   272
#define RAW_BPP      3
#define RAW_SIZE     (RAW_WIDTH * RAW_HEIGHT * RAW_BPP)

static int read_raw_rgb(void *buf, OMX_U32 *filledLen, int filenumber)
{
  char filename[256];
  FILE *f;
  size_t ret;

  snprintf(filename, 256, "%08d.png.raw", filenumber);

  f = fopen(filename, "r");
  if (!f) {
 printf("Failed to open '%s'\n", filename);
 return 0;
  }

  ret = fread(buf, 1, RAW_SIZE, f);
  if (ret < RAW_SIZE) {
 printf("Failed to read '%s': %d\n", filename, ret);
 return 0;
  }

  *filledLen = RAW_SIZE;

  fclose(f);
  return 1;
}

static void print_def(OMX_PARAM_PORTDEFINITIONTYPE def)
{
   printf("Port %lu: %s %lu/%lu %lu %lu %s,%s,%s %lux%lu %lux%lu @%lu %u\n",
          def.nPortIndex,
          def.eDir == OMX_DirInput ? "in" : "out",
          def.nBufferCountActual,
          def.nBufferCountMin,
          def.nBufferSize,
          def.nBufferAlignment,
          def.bEnabled ? "enabled" : "disabled",
          def.bPopulated ? "populated" : "not pop.",
          def.bBuffersContiguous ? "contig." : "not cont.",
          def.format.video.nFrameWidth,
          def.format.video.nFrameHeight,
          def.format.video.nStride,
          def.format.video.nSliceHeight,
          def.format.video.xFramerate,
          def.format.video.eColorFormat);
}

static int video_encode_test(char *outputfilename)
{
   OMX_VIDEO_PARAM_PORTFORMATTYPE format;
   OMX_PARAM_PORTDEFINITIONTYPE def;
   COMPONENT_T *video_encode = NULL;
   COMPONENT_T *list[5];
   OMX_BUFFERHEADERTYPE *buf;
   OMX_BUFFERHEADERTYPE *out;
   OMX_ERRORTYPE r;
   ILCLIENT_T *client;
   int status = 0;
   int filenumber = 0;
   FILE *outf;

   memset(list, 0, sizeof(list));

   if((client = ilclient_init()) == NULL)
   {
      return -3;
   }

   if(OMX_Init() != OMX_ErrorNone)
   {
      ilclient_destroy(client);
      return -4;
   }

   // create video_encode
   r = ilclient_create_component(client, &video_encode, "video_encode",
         ILCLIENT_DISABLE_ALL_PORTS |
         ILCLIENT_ENABLE_INPUT_BUFFERS |
         ILCLIENT_ENABLE_OUTPUT_BUFFERS);
   if (r != 0) {
      printf("ilclient_create_component() for video_encode failed with %x!\n", r);
      exit(1);
   }
   list[0] = video_encode;

   memset(&def, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
   def.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
   def.nVersion.nVersion = OMX_VERSION;
   def.nPortIndex = 200;

   if (OMX_GetParameter(ILC_GET_HANDLE(video_encode), OMX_IndexParamPortDefinition, &def) != OMX_ErrorNone) {
      printf("%s:%d: OMX_GetParameter() for video_encode port 200 failed!\n", __FUNCTION__, __LINE__);
      exit(1);
   }

   print_def(def);

// Port 200: in 1/1 115200 16 enabled,not pop.,not cont. 320x240 320x240 @1966080 20
   def.format.video.nFrameWidth = RAW_WIDTH;
   def.format.video.nFrameHeight = RAW_HEIGHT;
   def.format.video.xFramerate = 30 << 16;
   def.format.video.nSliceHeight = def.format.video.nFrameHeight;
   def.format.video.nStride = def.format.video.nFrameWidth;
   def.format.video.eColorFormat = OMX_COLOR_Format24bitBGR888;

   print_def(def);

   r = OMX_SetParameter(ILC_GET_HANDLE(video_encode), OMX_IndexParamPortDefinition, &def);
   if (r != OMX_ErrorNone) {
      printf("%s:%d: OMX_SetParameter() for video_encode port 200 failed with %x!\n", __FUNCTION__, __LINE__, r);
      exit(1);
   }

   memset(&format, 0, sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE));
   format.nSize = sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE);
   format.nVersion.nVersion = OMX_VERSION;
   format.nPortIndex = 201;
   format.eCompressionFormat = OMX_VIDEO_CodingAVC;

   printf("OMX_SetParameter for video_encode:201...\n");
   r = OMX_SetParameter(ILC_GET_HANDLE(video_encode), OMX_IndexParamVideoPortFormat, &format);
   if (r != OMX_ErrorNone) {
      printf("%s:%d: OMX_SetParameter() for video_encode port 201 failed with %x!\n", __FUNCTION__, __LINE__, r);
      exit(1);
   }

   printf("encode to idle...\n");
   if (ilclient_change_component_state(video_encode, OMX_StateIdle) == -1) {
  printf("%s:%d: ilclient_change_component_state(video_encode, OMX_StateIdle) failed", __FUNCTION__, __LINE__);
   }

#if 1
   printf("enabling port buffers for 200...\n");
   if (ilclient_enable_port_buffers(video_encode, 200, NULL, NULL, NULL) != 0) {
      printf("enabling port buffers for 200 failed!\n");
      exit(1);
   }

   printf("enabling port buffers for 201...\n");
   if (ilclient_enable_port_buffers(video_encode, 201, NULL, NULL, NULL) != 0) {
      printf("enabling port buffers for 201 failed!\n");
      exit(1);
   }
#endif

   printf("encode to executing...\n");
   ilclient_change_component_state(video_encode, OMX_StateExecuting);

   outf = fopen(outputfilename, "w");
   if (outf == NULL) {
  printf("Failed to open '%s' for writing video\n", outputfilename);
  exit(1);
   }

   printf("looping for buffers...\n");
   do
   {
      buf = ilclient_get_input_buffer(video_encode, 200, 1);
      if (buf == NULL) {
         printf("Doh, no buffers for me!\n");
      } else {
//         printf("Got buffer: %p %lu %lu %lu\n", buf, buf->nAllocLen, buf->nFilledLen, buf->nOffset);

         /* fill it */
   read_raw_rgb(buf->pBuffer, &buf->nFilledLen, ++filenumber);

         if(OMX_EmptyThisBuffer(ILC_GET_HANDLE(video_encode), buf) != OMX_ErrorNone) {
            printf("Error emptying buffer!\n");
         } else {
//            printf("Buffer emptied!\n");
         }

         printf("Requesting output...\n");
         fflush(stdout);
         out = ilclient_get_output_buffer(video_encode, 201, 1);

         r = OMX_FillThisBuffer(ILC_GET_HANDLE(video_encode), out);
         if (r != OMX_ErrorNone) {
            printf("Error filling buffer: %x\n", r);
         }

   // Debug print the buffer flags
   if (out->nFlags & OMX_BUFFERFLAG_CODECCONFIG) {
     printf("Got buffer flag: OMX_BUFFERFLAG_CODECCONFIG\n");
   }
   if (out->nFlags & OMX_BUFFERFLAG_ENDOFFRAME) {
     printf("Got buffer flag: OMX_BUFFERFLAG_ENDOFFRAME\n");
   }
   if (out->nFlags & OMX_BUFFERFLAG_SYNCFRAME ) {
     printf("Got buffer flag: OMX_BUFFERFLAG_SYNCFRAME\n");
   }
   if (out->nFlags & 0x400) {
     // FIXME: what is 0x400??
     printf("Got buffer flag: 0x400 flag\n");
   }
   if (out->nFlags & ~(OMX_BUFFERFLAG_CODECCONFIG | OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_SYNCFRAME | 0x400)) {
     printf("Got more buffer flag: %lx\n",
      out->nFlags & ~(OMX_BUFFERFLAG_CODECCONFIG | OMX_BUFFERFLAG_ENDOFFRAME | 0x400 | OMX_BUFFERFLAG_SYNCFRAME));
   }

         if (out != NULL) {
            printf("Got it: %p %lu\n", out, out->nFilledLen);
   if (out->nFlags & OMX_BUFFERFLAG_CODECCONFIG) {
     int i;
     for (i = 0; i < out->nFilledLen; i++)
    printf("%x ", out->pBuffer[i]);
     printf("\n");
   }

   r = fwrite(out->pBuffer, 1, out->nFilledLen, outf);
   if (r != out->nFilledLen) {
     printf("fwrite: Error emptying buffer: %d!\n", r);
   } else {
     printf("Buffer emptied!\n");
   }
   out->nFilledLen = 0;
         } else {
            printf("Not getting it :(\n");
         }

      }
   } while (filenumber < 184);

   fclose(outf);

   printf ("Teardown.\n");

   ilclient_state_transition(list, OMX_StateIdle);

   ilclient_cleanup_components(list);

   OMX_Deinit();

   ilclient_destroy(client);
   return status;
}

int main (int argc, char **argv)
{
   if (argc < 2) {
      printf("Usage: %s \n", argv[0]);
      exit(1);
   }
   bcm_host_init();
   return video_encode_test(argv[1]);
}

And the associated make file: 

OBJS=video.o
BIN=hello_video.bin
LDFLAGS+=-lilclient -lpthread

include ../Makefile.include

There really isn't any real application of the encoder yet, I think everyone is still waiting for the official camera module. From what I read the Pi is not able to cope with most USB camera module running at higher resolution (ie. 640x480) and will cause the Pi to crash. I personally tried a Microsoft Cinema HD and the video captured at 160x120 crashed roughly 10 seconds in. But regardless, this now allows the Pi to be used as a standalone transcoder to encode any data to h264 format. 



My VI Settings

Everyone has their own favorite editors. I use different editors at work (SlickEdit) and at home (Eclipse), but in either cases I use Vi emulation. 

The following is my Vi settings (~/.vimrc):

set nocompatible
set number
set smartindent
set tabstop=4
set shiftwidth=4
set expandtab
set autoindent
set hlsearch
colorscheme koehler
set guifont=courier_new:h10
syntax on