2014-06-30

高清機頂盒轉換記3之《終於放棄8仔》


自從在2011年將8仔的電源組件換了新之後,機身溫度問題解決了(見高清機頂盒轉換記2之《回歸8仔》)。但三年後,可能由於零件老化(8仔質量問題?),加上近來天氣熱,部8仔又傻傻地(機身很熱),時常錄影不到電視節目,病徵包括錄影不到全個節目(例如一小時節目只錄到半小時),個鐘short左將一還未夠鐘的節目錄低(誤差有成半日),hang機,認不到USB硬碟(但我用硬碟diagnositic工具找不到問題)。

錄影不到節目的結果是被老婆大人投訴,因為不是所有TVB的節目都能在mytv睇得番(可能是網上版權問題),試過真是有次有劇集的大結局不能錄到而不高興。

其實我真有點喜歡8仔的User Interface,所以我有考慮買另一部8仔,但是網上評論真是麻麻。而由於上次2010年ACL經驗(見高清機頂盒轉換記),我也不敢再次其他雜嘜牌子了。但是現在幾乎所有電視機已內置高清解碼器,市場上高清機頂盒的選擇真是不多(反而那些網上播放器就大行其道)。

其實我的要求很簡單,我只是需要一部可同時錄播電影台節目的機頂盒,最好有dual tuner,其他古怪功能就不必。但是質素要隱定,因我覺得時間比金錢寶貴,我不希望我再要因錄影失敗要我花時間上網尋找再download節目(這亦是我不看直播節目的原因,我可以「飛」廣告和遇到不喜歡的錄影便直接delete了事)。

終於我買了Magic TV的3300D(連結


價錢很貴,參考零售價是$1,880。我在格價網最平也要$1,700。這model是不連hard disk的(否則要再貴多一千元!),我在買機是亦被店員揶揄這個價錢連電視機都買得。

現在用了一星期,表現大致滿意:

  • 反應速度okay
  • 同時錄播表現穩定
  • 機身溫度不高
  • 雖然Standby我選用「所有關閉」,但開機速度也算滿意
  • 我特別喜歡它也有8仔的提前開始時間/延長結束時間
  • 而「飛」廣告也可用SKIP掣直接向前跳過30秒(8仔是一分鐘,而廣告時段一定是其倍數)


價錢真是很貴,希望它長命百歲!

2014-04-01

My experience to trouble shoot a notebook computer powering off suddenly


I like programming (hence debugging) but I am not particular interested in trouble-shooting PC installation issues.

A friend of my wife has recently got a second hand notebook PC for his son.  However, the machine will power off randomly, sometimes as early as 1 minute right after power on.  However, if the PC is is booted in Windows Safe mode, the symptom will disappear.

The PC configuration is as follows:

Model: LG R405-A
Operating System: Windows 7 Enterprise
Video card: ATI Radeon Xpress 1250

Why I highlight the Video card because it was also reported there will be AppCrash (about atiundag,dll) when the Powerpoint slideshow mode is activated (and also within Desktop Window Manager (DWM) when the Windows starts).

Therefore my first impression is about the ATI driver version.  However, Windows Update shows it is already running the latest version.

Unbelieving this fact, I go to the ATI web site (now AMD) to download some new version of drivers.  After rebooting, the AppCrash still appears.  Worst still, the random power off problem also still occurs.

However, I notice that right before the powering off, I can hear a sudden increase of the ventilation fan noise.  In fact, from some internet forums, some people has already said that this symptom may be due to overheating.  Then I install some temperature monitoring software and I find that the CPU temperature rises gradually to nearly 100°C and then the machine will shut down.  Then I realize that the previous so called randomness is just due to my testing pattern.  If the machine is off whole night and then power up, then it can sustain longer until the temperature reaches the threshold.  But if I repeatedly reboot the machine and do not wait for it to cool down, of course the temperature problem is not solved and it can reach the threshold within 1 minute after booting up.

At this stage, I still think the ATI driver is the root cause of temperature.  Then I try to go to the LG web site to try to download the ATI driver.  Because Xpress 1250 is a really old chip, in fact, there is no Windows 7 driver for it.  In the LG web site, the latest version is only for Vista.  I did download to try anyway.

To my surprise, the AppCrash problem is solved immediately with the Vista driver.

However, before I can feel happy, the machine turns off again suddenly!

Then I start to think whether there is any hardware issue (e.g. blocked ventilation or bad cooling agent) that causes the high temperature.  I do not want to dismantle the notebook PC.  So, I resort to use the Windows Power Plan to limit the maximum CPU utilization to 50%.  Okay, then the temperature is only around 85°C and does not crash again even though I run YouTube to play video repeatedly.  It seems that the problem is solved (although not fundamentally but at least superficially).

I try to check if there is any fan control utility that can force the fan to always on (I have already setup the BIOS setting but the fan speed is not high).  When I use the SpeedFan software, I notice suddenly that both cores of the CPU is running at 100%.

When I check the task manager, I find that a process dgen.exe is eating my CPU.  It is a virus.  How come I never think about this?  (At a second thought, although the CPU is at 100%, it is not sluggish and still very responsive.)  Afterwards, I update the anti-virus engine and eradicate any other remaining viruses found in the computer.

Postscript: Although the root cause is traced now, I still cannot understand how come the LG computer is so designed that if the CPU keeps on running at 100% loading, the cooling mechanism is not sustainable to keep the temperature within the working limit.

2014-03-04

懷念舅公關傑才先生


在剛過去的農曆新年,和母親提起小時候會去澳門同外祖父母拜年。而且澳門還有一位舅公,他在澳門有一所英語學校,但我年紀小,沒有聽過他說英文,不過他曾送我一本他用中文寫的英文工具書,書名叫《英文解字》,這書的包裝很特別,是一本線裝書!



忽然間有點懷念這一位舅公,但對他的認識不深,只好靠互聯網找找有關他的資料。原本他也編寫過幾本書,其中一本《英譯廣東口語詞典》,在台灣博客來上的作者簡介如下:


關傑才


資深教育工作者,翻譯工作者。先後擔任過中學教務主任;中學高年級英文教師;公開大學特約講師及文字翻譯等職。有豐富的教中國人學英語和教外國人學廣東話的教學經驗。曾編譯出版《英文解字》一書,本書為其又一力作。

而另一編
趙汝能寫給關傑才《詞語疑音字正讀》的序
則見於: http://bbs.qoos.com/viewthread.php?tid=1544660


我在書櫃再找出他送我的《英文解字》線裝書,由於在網上再找不到此書的資料,故我決定將此孤本數碼化,以作紀念!

http://waihungmm.web.fc2.com/english/

另外,下載PDF可於https://drive.google.com/file/d/0Bzo1pJKfp9QELWFKTjlSNUUtZWM/view?usp=sharing



2014-01-22

Detecting errors in jpeg file using libjpeg-turbo

Recently I have the need to detect the integrity of a jpeg file. First I try to see the famous libjpeg (wiki) library can do the job. I am using Visual Studio (i.e. windows) to write my codes and I download the win32 version of libjpeg from internet. However, I keep on failure because the win32 libjpeg library keeps on emitting "Access Violation" at runtime. I try to solve the problem by googling solution but in vain.

Later I turn to use libjpeg-turbo although I really do not need to performance of SIMD instructions. Luckily libjpeg-turbo has a Visual Studio version of static library and I do to encounter the runtime error in libjpeg any more.

Basically my codes should be simply, as illustrated by the following pseudo-codes:

jpeg_create_decompress();
fopen(jpeg_file);
jpeg_stdio_src();
jpeg_read_header();
jpeg_start_decompress();
while loop {
  jpeg_read_scanlines();
  }
jpeg_finish_decompress();
jpeg_destroy_decompress();
fclose();

However, to my surprise, libjpeg use a setjmp/longjmp approach to detect and handle the errors. Frankly speaking, although I have been using C languages for years, I really do not like the GOTO style of handling errors. The libjpeg is emulating an Object-oriented approach by allowing programmer to build an error hander to intercept the errors.

I am interested in two kinds of errors:
  • Fatal errors: the library will quit in this case
  • Warnings: the library can continue with data corruption
The former method is called "error_exit()" and hte latter will invoke "output_message()". So, my job is to build my version of error_exit() and output_message(), with pseudo codes as follows:

my_error_exit ()
{

  /* store the error code in the global variable */
  /* store the error message in the global variable */
  longjmp (myerr->setjmp_buffer, 1); /* Return control to the setjmp point */
}

my_output_message ()
{
  /* store the error code in the global variable */
  /* store the error message in the global variable */
}

The full source listing is as follows:

#include 
#include "stdafx.h"
#include "jpeglib.h"
#include 
#include 

int error_message_code;
char error_message_buffer[JMSG_LENGTH_MAX];

struct my_error_mgr {
  struct jpeg_error_mgr pub;
  jmp_buf setjmp_buffer;
};

typedef struct my_error_mgr * my_error_ptr;

METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  my_error_ptr myerr = (my_error_ptr) cinfo->err;
  error_message_code = cinfo->err->msg_code; /* store the code in the global variable */
  (*cinfo->err->format_message) (cinfo, error_message_buffer);  /* store the message in the global variable */
  printf ("Error intercepted in my_error_exit(): error_code = %d message = \'%s\'\n",
   error_message_code, error_message_buffer);
  longjmp(myerr->setjmp_buffer, 1); /* Return control to the setjmp point */
}

METHODDEF(void)
my_output_message (j_common_ptr cinfo)
{
  error_message_code = cinfo->err->msg_code;
  (*cinfo->err->format_message) (cinfo, error_message_buffer);
}

int _tmain(int argc, _TCHAR* argv[])
{
  struct jpeg_decompress_struct cinfo;
  struct my_error_mgr jerr;
  FILE * infile;
  JSAMPARRAY ptr;  /* Output row buffer */
  int row_stride;  /* physical row width in output buffer */

  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;
  jerr.pub.output_message = my_output_message;
  /* Establish the setjmp return context for my_error_exit to use. */
  if (setjmp(jerr.setjmp_buffer)) {
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);
    return 0;
  }

  jpeg_create_decompress(&cinfo);

  if ((infile = fopen("C:\\bin\\libjpeg-turbo\\b.jpg", "rb")) == NULL) {
 printf("cannot open jpeg file");
 return 1;
 }

  jpeg_stdio_src(&cinfo, infile);

  (void) jpeg_read_header(&cinfo, TRUE);

  jpeg_start_decompress(&cinfo);

  row_stride = cinfo.output_width * cinfo.output_components;
  ptr = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo,JPOOL_IMAGE,row_stride,1);
 
  while (cinfo.output_scanline < cinfo.output_height) {
    error_message_code = 0;
    jpeg_read_scanlines(&cinfo, (JSAMPARRAY) ptr, 1);
    if (error_message_code) 
       printf("Error occured at scanline %d: Error code = %d message= \'%s\'\n",
         cinfo.output_scanline, error_message_code, error_message_buffer);
    }  // while

  (void) jpeg_finish_decompress(&cinfo);

  jpeg_destroy_decompress(&cinfo);

  fclose (infile);

  return 0;
}

Postscript

I prepare two corrupted jpeg files. The first is truncated file that the exact image height is smaller than that is specified in the jpeg header. In this case, I can successfully intercept the error at scanline 313 with Error code = 120 and error message = "Premature end of JPEG file". The second is truncated that even the jpeg header cannot be successfully read. In this case, my my_error_exit() terminating route can capture the error_code = 51 and error message = "JPEG datastream contains no image".

2013-10-28

南蓮園池之微距攝影

每次都有不同感覺,今次玩Macro


















2013-10-03

雪糕甜品

如果不是老婆有coupon,我也不捨得花錢買咁貴的雪糕吃!


2013年溫暖人間晚宴

幸得朋友包了一圍,令我有機會出席這盛大又溫馨的晚宴。




2013-10-01

肥陶人@油塘

近來連續幾個週末都去了油塘大本型商場試新食市,要小心身體會變成像港鐵站外的肥陶人!


2013-08-27

Creating a Full Blog list Gadget

I try to create a full blog list.  Originally I thought Google should have a built-in gadget for this purpose but I was wrong.   So finally after reviewing other blogger's proposal, I implement my way as follows:



(1) From the Layout menu, choose "Add a Gadget"







(2) Choose the "HTML/Javascript" Gadget







(3) Input the Title and enter the HTML in the Content box







-----
<div id="bp_toc"></div>
<script>
// ---------------------------------------------------
// BLOGTOC
// ---------------------------------------------------
// BlogToc creates a clickable Table Of Contents for
// Blogger Blogs.
// It uses the JSON post feed, and create a ToC of it.
// The ToC can be sorted by title or by date, both
// ascending and descending, and can be filtered by
// label.
// ---------------------------------------------------
// Author: Beautiful Beta
// Url: http://beautifulbeta.blogspot.com
// Version: 2
// Date: 2007-04-12
// ---------------------------------------------------
// Modified by Aneesh
// www.bloggerplugins.org
// Date : 02-08-2011
// ---------------------------------------------------
// Modified by waihungmm
// Date : 2013-08-21

   var postTitle = new Array();     // array of posttitles
   var postUrl = new Array();       // array of posturls
   var postDate = new Array();      // array of post publish dates
   var postSum = new Array();       // array of post summaries
   var postLabels = new Array();    // array of post labels

// global variables
   var sortBy = "titleasc";         // default value for sorting ToC
   var tocLoaded = false;           // true if feed is read and ToC can be displayed
   var numChars = 250;              // number of characters in post summary
   var postFilter = '';             // default filter value
   var tocdiv = document.getElementById("bp_toc"); //the toc container
   var totalEntires =0; //Entries grabbed till now
   var totalPosts =0; //Total number of posts in the blog.

// main callback function

function loadtoc(json) {

   function getPostData() {
   // this functions reads all postdata from the json-feed and stores it in arrays
      if ("entry" in json.feed) {
         var numEntries = json.feed.entry.length;
         totalEntires = totalEntires + numEntries;
         totalPosts=json.feed.openSearch$totalResults.$t

      // main loop gets all the entries from the feed
         for (var i = 0; i < numEntries; i++) {
         // get the entry from the feed
            var entry = json.feed.entry[i];

         // get the posttitle from the entry
            var posttitle = entry.title.$t;

         // get the post date from the entry
            var postdate = entry.published.$t.substring(0,10);

         // get the post url from the entry
            var posturl;
            for (var k = 0; k < entry.link.length; k++) {
               if (entry.link[k].rel == 'alternate') {
               posturl = entry.link[k].href;
               break;
               }
            }

         // get the post contents from the entry
         // strip all html-characters, and reduce it to a summary
            if ("content" in entry) {
               var postcontent = entry.content.$t;}
            else
               if ("summary" in entry) {
                  var postcontent = entry.summary.$t;}
               else var postcontent = "";
         // strip off all html-tags
            var re = /<\S[^>]*>/g;
            postcontent = postcontent.replace(re, "");
         // reduce postcontent to numchar characters, and then cut it off at the last whole word
            if (postcontent.length > numChars) {
               postcontent = postcontent.substring(0,numChars);
               var quoteEnd = postcontent.lastIndexOf(" ");
               postcontent = postcontent.substring(0,quoteEnd) + '...';
            }

         // get the post labels from the entry
            var pll = '';
            if ("category" in entry) {
               for (var k = 0; k < entry.category.length; k++) {
                  pll += '<a href="javascript:filterPosts(\'' + entry.category[k].term + '\');" title="Click here to select all posts with label \'' + entry.category[k].term + '\'">' + entry.category[k].term + '</a>,  ';
               }
            var l = pll.lastIndexOf(',');
            if (l != -1) { pll = pll.substring(0,l); }
            }

         // add the post data to the arrays
            postTitle.push(posttitle);
            postDate.push(postdate);
            postUrl.push(posturl);
            postSum.push(postcontent);
            postLabels.push(pll);
         }
      }
      if(totalEntires==totalPosts) {tocLoaded=true;showToc();}
   } // end of getPostData

// start of showtoc function body
// get the number of entries that are in the feed
//   numEntries = json.feed.entry.length;

// get the postdata from the feed
   getPostData();

// sort the arrays
   sortPosts(sortBy);
   tocLoaded = true;
showToc(); // newly added
}

// filter and sort functions


function filterPosts(filter) {
// This function changes the filter
// and displays the filtered list of posts
  // document.getElementById("bp_toc").scrollTop = document.getElementById("bp_toc").offsetTop;;
   postFilter = filter;
   displayToc(postFilter);
} // end filterPosts

function allPosts() {
// This function resets the filter
// and displays all posts

   postFilter = '';
   displayToc(postFilter);
} // end allPosts

function sortPosts(sortBy) {
// This function is a simple bubble-sort routine
// that sorts the posts

   function swapPosts(x,y) {
   // Swaps 2 ToC-entries by swapping all array-elements
      var temp = postTitle[x];
      postTitle[x] = postTitle[y];
      postTitle[y] = temp;
      var temp = postDate[x];
      postDate[x] = postDate[y];
      postDate[y] = temp;
      var temp = postUrl[x];
      postUrl[x] = postUrl[y];
      postUrl[y] = temp;
      var temp = postSum[x];
      postSum[x] = postSum[y];
      postSum[y] = temp;
      var temp = postLabels[x];
      postLabels[x] = postLabels[y];
      postLabels[y] = temp;
   } // end swapPosts

   for (var i=0; i < postTitle.length-1; i++) {
      for (var j=i+1; j<postTitle.length; j++) {
         if (sortBy == "titleasc") { if (postTitle[i] > postTitle[j]) { swapPosts(i,j); } }
         if (sortBy == "titledesc") { if (postTitle[i] < postTitle[j]) { swapPosts(i,j); } }
         if (sortBy == "dateoldest") { if (postDate[i] > postDate[j]) { swapPosts(i,j); } }
         if (sortBy == "datenewest") { if (postDate[i] < postDate[j]) { swapPosts(i,j); } }
      }
   }
} // end sortPosts

// displaying the toc

function displayToc(filter) {
// this function creates a three-column table and adds it to the screen
   var numDisplayed = 0;
   var tocTable = '';
   tocTable += '<table>';

   tocTable += '</tr>';
   for (var i = 0; i < postTitle.length; i++) {
         tocTable += '<tr><td class="toc-entry-col1"><a href="' + postUrl[i] + '" target=_blank>' + postTitle[i] + '</a></td></tr>';
         numDisplayed++;
   }
   tocTable += '</table>';

tocdiv.innerHTML = tocTable;
} // end of displayToc

function showToc() {
  if (tocLoaded) {
     displayToc(postFilter);
     var toclink = document.getElementById("toclink");
 
  }
  else { alert("Just wait... TOC is loading"); }
}

</script>
<script src="http://your_blog_name.blogspot.com/feeds/posts/default?alt=json-in-script&max-results=999&callback=loadtoc"></script>
-----



(4) The result will be as follows





(5) This is the final output:

2013-04-03