Raspberry PI transcodes video

1. Transcode là gì.
Transcode là quá trình chuyển đổi từ định dạng mã hóa hiện tại sang một dạng mã hóa khác, ví dụ như chuyển đổi video, audio coding format …
Thông thường thì việc thực hiện transcode có 2 lý do chính:
– Giảm dung lượng lưu trữ mà có thể đảm bảo được chất lượng video, bằng việc sử dụng video coding format tân tiến hơn, tăng khả năng nén hơn, nên các dữ liệu video đang sử dụng coding format cũ thường chuyển sang format mới để giảm dung lượng lưu trữ.
– Thiết bị đầu cuối không hỗ trợ video or audio hiện tại, do đó cần convert sang cái dạng mà nó hỗ trợ.

Trên thực tế hiện nay thì dễ thấy nhất là khi upload 1 file video lên facebook hoặc youtube, thì file video đó luôn được youtube/facebook xử lý thành định dạng khác trong quá trình upload.

Cơ bản thì quá trình transcode có thể hình dung đơn giản như dưới.

input file/stream → |decode| → |encode| → output file/stream

Từ dữ liệu đầu vào, có thể là multimedia file format (MKV, MP4 …) hoặc stream (udp, tcp/ip …). Bước đầu tiên là giải mã (decode) thành phần đầu vào (video or audio tùy thuộc vào cái nào cần transcode) thành raw data. Tiếp đó thực hiện mã hóa (encode) thành định dạng mới ở dữ liệu đầu ra.

Transcode có thể realtime or non-realtime tùy thuộc vào từng yêu cầu của hệ thống. Trong các dữ liệu cần transcode thì video là phức tạp, tốn nhiều hiện năng nhất, và thường quan tâm nhất , do việc nén video theo chuẩn mới có thể tiết kiệm rất nhiều dung lượng or bitrate.

2. Video decoder/encoder trên RPI.
Raspberry pi(RPI) hỗ trợ cả hardware decoder lẫn encoder, do đó rất phù hợp nếu sử dụng nó để thực hiện transcode.
2 bộ decoder và encoder trên RPI được thiết kế theo API của openmax mà cụ thể là openmax-il.
Openmax là chuẩn các api do khronos đưa ra để thiết kế hệ thống xử  lý multimedia, nó gồm có 3 phần tương được với 3 layer, nhìn hình dưới lấy từ trang chủ có nó thì hiểu, khỏi cần giải thích.
media_portability
Cái gì mà được chuẩn hoá thì mục đích cao nhất của nó là giúp giảm thời gian tích hợp, 2 thằng không cần biết nhau trước đó mà chỉ cần cùng tuân theo 1 chuẩn là dễ dàng tích hợp với nhau.
Openmax-il có thể hiểu là lớp trung gian giữa phần cứng (hardware) và application hay còn gọi là firmware, đây cũng là phần API được sử dụng rộng rãi nhất của openmax. Android media framework, ffmpeg, gstreamer đều đã hỗ trợ openmax-il với tư cách là IL client, tức là layer ngay ở trên openmax-il . Do đó nếu bạn rảnh rỗi thiết kế 1 con chip hỗ trợ video decoder tuân thủ theo đúng api của openmax-il thì bạn có thể nhanh chóng tích hợp được vào các hế thống trên :-), và bán con chip đó luôn được để kiếm xèng.

Chi tiết openmax thì vào trang chủ của nó để tham khảo ở đây[3].
Thông tin các openmax-il component trên RPI thì tham khảo chỗ này[5].

3. Thực hiện transcode video trên RPI.
3.1. Thực hành transcode với RPI.
Omxplayer là cli hoạt động rất ổn định khi dùng play video trên RPI. Mình vẫn dùng để mở clip tàu điện Nhật Bản phục vụ ông con thích tàu xe.
Omxplayer sử dụng hardware decoder của RPI, và tất nhiên là thông qua openmax-il, cấu trúc của nó như hình dưới.
dec_rpi

Xuất phát từ ý tưởng dùng RPI để chuyển đổi video streaming mpeg2-video đang có sẵn thành h.264 để giảm bitrate nhằm tiết kiệm băng thông. Dựa trên omxplayer
với một chút sửa đổi nhỏ sau:
– Bỏ audio decoder, vì audio chiếm băng thông không đáng kể, không cần thiết transcode nên audio vào thế nào cho ra thế đó.
– Cắt bỏ phần xử lý hiện thị (render component), vì chỉ cần chuyển đổi sang video format khác, kết quả sẽ streaming hoặc lưu thành file.
– Thêm component encoder, nối vào ngay sau decoder. Để thực hiện encode lại ảnh, kết quả của bộ decoder sau khi giải mã đầu vào.
– Thêm phần xử lý output streamer, module này có nhiệm vụ lưu audio + video sau khi convert thành file hoặc streaming qua udp hoặc tcp/ip hoặc protocol nào đấy.

Sau khi sửa đổi thì cấu trúc nó thay hình đổi dạng như dưới.
transcode_rpi

Source code & readme hướng dẫn sử dụng chứa ở đây

Kết quả chạy thử, đạt được realtime và bitrate giảm 1 nửa.
input http streaming: mpeg2-video,size 720×576, 25fps, bitrate 4mbps.
output udp streaming: h.264, size 720×576, 25fps, bitrate 2mbps.

Khi play stream gốc và stream sau khi đã transcode thì chất lượng theo đánh giá chủ quan là tương đương nhau. Cần chú ý rằng, đây chỉ là về mặt visual, còn nén video là nén mất dữ liệu (lossly) do đó cái output không bao giờ bằng được input nếu nói trên quan điểm thông tin.

●Hiện tại đang có các điểm hạn chế cần điều tra và cải thiện:
– Chạy được tầm 1h ~ 2h thì tự dưng lăn quay là treo → bug cần điều tra.
– Openmax-il có định nghĩa 2 kiểu kết nối để component truyền data cho nhau, là tunnel (kết nối trực tiếp) và non-tunnel (kết nối gián tiếp thông qua IL client). Định dùng tunnel cho đơn giản, nhưng thử mãi không được nên đang dùng non-tunnel, thành ra quản lý buffer đang hơi stupid nên output buffer của decoder được copy sang encoder. → Cần điều tra lại đển dùng tunnel hoặc improve việc quản lý buffer để bỏ xử lý copy.
–  Phần output streamer module cần design là 1 thread độc lập, nhận video/audio buffer ở component trước đó thông qua queue, nhưng hiện tại đang thực hiện như bằng context của video & audio (được buffer nào thì streaming luôn).

3.2. Dùng ffmpeg.
Đợt này có chút thời gian, định quay lại xử lý mấy hạn chế & bug của 3.1 thì mới để ý thấy ffmpeg nó cũng support openmax rồi, như vậy đơn giản thế này thôi.
– Clone ffmpeg và tham khảo chỗ này để enable omx khi compile.
– Nếu ngại complile từ source code thì có thể cài đặt ffmpeg thông qua repo của RPI, package đã được enable omx: sudo apt-get install ffmpeg

Dùng command dưới là xong :-).

ffmpeg -c:v mpeg2_mmal -i http://address_input -c:a copy -c:v h264_omx -b:v 2000k -f mpegts udp://address:port

Tuy nhiên có vẻ có vấn đề với input streaming interlace, ngoài ra chưa stress test nên chưa biết thế nào. Từ POC (proof-of-concept) đến Mass Product là một khoảng cách lớn, haizz …

●24/07/2018 update: Input stream đang sử dụng là interlace, trong khi đó bộ encoder h.264 trên RPI không support interlace nên aspect ratio output stream bị sai. Có thể cần xử lý de-interlace trước khi encode. Hoặc với video hiện tại thì có thể dùng encoder bằng software encoder (libx264) như command dưới, sử dụng option encode performance gần cao nhất, với video size 720×576 thì vẫn đạt được realtime, tuy nhiên con chip nóng quá, không dám chạy stress-test vì sợ nó ngỏm.

ffmpeg -c:v mpeg2_mmal -i http://address_input
-c:v libx264 -preset superfast -f mpegts -b:v 2000k -s 720×576
udp://address:port

P/S: Lưu ý, RPI mặc định chưa enable hardware mpeg2-video codec, muốn dùng phải bỏ tiền ra mua ở đây.

Tham khảo.
[1]https://en.wikipedia.org/wiki/Transcoding
[2]https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=72260
[3]https://www.khronos.org/openmax/
[4]https://source.android.com/devices/media/
[5]http://www.jvcref.com/files/PI/documentation/ilcomponents/
[6]https://github.com/popcornmix/omxplayer
[7]https://github.com/truongpt/rpi_transcode

Từ khóa register trong C

Trong lập trình nhúng (embedded programming), thi thoảng hay bắt gặp từ khóa register, thường là những chỗ tính toán loằng ngoằng, bit biếc dịch ngược dịch xuôi. Register được dùng đặt trước kiểu dữ liệu khi khai báo biến. Tác dụng của từ khóa register, nói một cách ngắn gọn là làm tăng hiệu năng(performance) của chương trình.

Thêm cái của nợ này vào thì tại sao có thể tăng được hiệu năng?. Mà thực sự tăng thật thì tăng được bao nhiêu?
Để thêm phần sinh động, thử một ví dụ nhỏ dưới xem nó có ra cái gì không?

void main()
{
clock_t start, end;
double t;
int i; // không dùng register
//register int i; // sử dụng register
start = clock();
while(i < 0xFFFFFFFF) i++;
end = clock();

t = ((double) (end – start)) / CLOCKS_PER_SEC;
printf("time used %f\n",t);
}

Biên dịch với gcc version 6.3.0
Chạy trên Raspberry Pi 2B (ARMv7 Processor rev 5 (v7l)).
– Sử dụng từ khóa register: time used 9.583080 giây
– Không sử dụng từ khóa register: time used 38.256520 giây

Ví dụ trên cho thấy dùng từ khóa register, thì hiệu năng nó tăng thật, riêng trong trường hợp này thì cũng đang kể đấy chứ nhỉ :-).Tất nhiên cải thiện được bao nhiêu thì phải tùy vào hoàn cảnh cụ thể khi sử dụng, mức độ sử dụng biến đó … Nhưng mà ngon lành vậy thì toàn bộ khai báo biến trong chương trình cứ mặc định thêm luôn từ khóa này thì chuyện gì xẩy ra?.
Để hiểu được điều đó thì trước hết thử xem tại sao hiệu năng nó tăng?
Quay lại với vấn đề cơ bản hơn một chút, trong kiến trúc của vi xử lý thì ALU (Arithmetic Logic Unit) là con trâu đóng vai trò xử lý các tính toán số học. Dữ liệu đưa vào làm việc với ALU phải chứa trong một vùng đặc biệt, gọi là các thanh ghi(register), và ALU chỉ làm việc với đống thanh ghi đó. Trong khi đó các biến khai báo trong chương trình thì đặt ở bộ nhớ ngoài (RAM chẳng hạn …). Do đó với khai báo biến thông thường, để thực hiện một phép tính thì cần có 3 bước.
① Nạp giá trị từ vùng nhớ chứa biến vào register
➁ Yêu cầu ALU xử lý register vừa được nạp giá trị.
③ Đưa kết quả vừa xử lý của ALU ra ngoài vùng nhớ chứa biến.
Hình dung thô thiển như hình vẽ dưới đây.

alu

Khi thêm từ khóa register để khai báo biến, thì tức là ta đã yêu cầu trình biên dịch ưu tiên đặc biệt dành luôn vùng register để chứa biến đó. Và hiển nhiên khi thực hiện tính toán trên biến đó thì giảm được bước ①&③, giảm bớt thủ tục thì hiệu năng nó tăng lên là chuyện dễ hiểu :-).
Quay lại với ví dụ trên, để đảm bảo đúng như thánh phán, thử thêm option “-save-temps” để lấy tập mã lệnh assembly xem nó có thật vậy không.

gcc register.c -o test -save-temps

Đoạn mã assembly ở dưới tương ứng với vòng while loop ở ví dụ trên. Dễ thấy chỗ bôi màu đó khi không dùng từ khóa register tương ứng với bước ①&③ ở trên. Còn khi dùng từ khóa register thì trình biên dịch nó dùng luôn thanh ghi r4 cho việc chứa biến i.
p/s: Nếu không dễ thấy thì tự tra cứu lại lệnh assembly của ARM

Không sử dụng từ khóa register

ldr r3, [fp, #-8] // bước ①
add r3, r3, #1
str r3, [fp, #-8] // bước ③
.L2:
ldr r3, [fp, #-8]
cmn r3, #1
bne .L3

Sử dụng từ khóa register

.L3:
add r4, r4, #1
.L2:
cmn r4, #1
bne .L3

Đến đây, ta thấy rõ ràng khi dùng từ khóa register, thì thay vì dùng bộ nhớ ngoài đển lưu biến thì chương trình sẽ sử dụng luôn register để lưu biến đó. Cái gì ngon, quí thì dĩ nhiên hiếm, register cũng thấy, số lượng register rất nhỏ so với bộ nhớ ngoài, mà đây còn là tài nguyên dùng chung. Do đó không thể chơi kiểu vô tổ chức để thằng nào thích lấy làm đồ riêng thì lấy được. Tùy từng tình huống, yêu cầu để lựa chọn phần xử lý nào nên sử dụng register để tăng hiệu năng mà có thể chấp nhận cho nó xin một vài register về làm của riêng.
Nếu không tin thì thử thêm đoạn code này “register int k[100*1024*1024];” vào
để chiếm register xem chương trình xem nó có ngỏm không ^.^

Why emacs

1.Programming editor.
Ngoài việc đọc, sửa mã nguồn, programming editor còn cần một số tính năng giúp lập trình viên dễ dàng khi lập trình (code brower), ví dụ: jump to function, auto complete.
Cá nhân bạn đang sử dụng cái nào cho việc công việc lập trình? về cơ bản bất cứ phần mềm nào xử được file text thì đương nhiên đều có thể dùng để viết code, ví dụ notepad chẳng hạn.
Lập trình cho window thì dùng Visual studio kinh điển, cho macOS hay iOS thì Xcode sang chảnh, cho Android thì có Eclipse. Với những trường hợp này thì chỉ editor chưa đủ, mà cần đầy đủ môi trường phát triển (trình biên dịch tích hợp, trình gỡ rối …), nên gần như lập trình viên không có lựa chọn nào khác.

Vậy với lập trình viên nhúng (embedded software) thì sao? Biên dịch mã thường bằng dòng lệnh trên console hoặc các gói phần mềm chuyên dụng đi kèm (thường ko tích hợp đủ tính năng viết/đọc code dễ dàng). Do đó có thể dùng bất cứ editor nào tuỳ thích.

Dĩ nhiên lập trình viên nhúng vẫn có thể dùng Visual studio hay Eclipse …, không vấn đề gì cả, và trên thực tế số đông làm vậy. Nói chung không có cái nào tốt hơn cái nào cả, quan trọng năng suất công việc, bạn đang quen dùng cái nào thì với bạn cái đó tốt nhất.

Mình bắt đầu đi làm năm 2009, công việc về nhúng, mới vào được các ông anh chỉ cho SourceInsight thần thánh, nhỏ gọn, chạy cực nhanh, còn graph hoá quá trình liên kết hàm cực kì lợi hại. Thích nó đến mức không có SourceInsight là không muốn đọc code nữa, rất bị phụ thuộc.

Năm 2013, qua Nhật công tác dài hạn ở công ty khách hàng. Không có SourceInsight vì vấn đề bản quyền, rất đau khổ nhưng cũng tìm phương án thay thế bằng Eclipse, tuy hơi nặng nhưng cũng dùng ổn, đủ các tính năng cần thiết, dùng mãi rồi cũng quen.

Từ 2016 đến nay đổi sang Emacs.

2.Why emacs.
Đã nghe kể kỹ sư ở Nhật Bản toàn dùng Emacs hoặc Vim. Cũng đã đôi lần thử dùng nhưng cũng chỉ sửa vài cái nho nhỏ trên console, chứ không hiểu làm sao dùng đồ cổ này mà đọc được đống code. Sang Nhật làm việc thì đúng thế thật, toàn Emacs rồi thì Vim, Sakura (một loại editor) cũng lắm thằng dùng.
Cậu người Nhật làm cùng, sử dụng Emacs nên thi thoảng mình nửa đùa nửa thật, tao muốn dùng Emasc nhưng mãi không dùng được. Rồi đến một hôm đẹp trời, nó quảng cho các cục Emacs đã có thiết lập một số thứ cơ bản, rồi sang chỉ trỏ cho mình. Nó nhiệt tình quá, nên cũng thử cố xem sao. Lúc đầu chưa quen thì đúng là như đi tù luôn, nhưng thật kì diệu, sau tầm 1-2 tuần gì đó, cho đến giờ là kết luôn con Emacs.

emacs

Hỏi google, ta sẽ có 1001 lý do tại sao nên dùng Emacs, tất nhiên chắc cũng có tầm đó lý do tại sao không cần thiết hoặc không nên dùng chăng. Do đó cãi nhau editor nào ưu việt cho lập trình viên là không cần thiết.
Đối với cá nhân mình, dùng Emacs vì một vài lý do:
– Trông nguy hiểm, màn hình đen sì toàn code là code.
– Kết hợp với Gtag, có thể cung cấp các chức năng như code brower.
– Nhẹ, chạy được trên máy cấu hình không cần cao quá, phù hợp với kỹ sư VN (nghèo).
– Bỏ được con Chuột khi viết/đọc code (mình tuổi Mèo nên không ưa Chuột).
– Chạy trên mọi nền tảng từ Windows, Linux đến macOS.
– Tuỳ biến cao, có thể dùng LISP để tự viết thêm tính năng. (mình chưa làm thử, do chưa biết LISP).
– Không chỉ là editor, mà còn là nhiều thử khác (tự google để biết).

3. Thiết lập cơ bản.
Để đủ dùng Emacs đọc, viết code thì có thể thiết lập đơn giản trên Windows.
– Tải Emacs tại đây
– Tải gtags tại đây về, giải nén và sửa PATH environment để thêm đường dẫn tới thư mục bin của gtag.
– Tự google để biết cách thiết lập với gtags, hoặc lấy cái mình đã thiết lập ở đây , đè lên thư mục .emacs.d ( xem [2] để biết chỗ để thư mục .emacs.d).
– Xem [3] để biết cách sử dụng Emacs với gtags để đọc code.
– Đọc tài liệu chính thống ở đây, hoặc chế độ help, hoặc google khi không rõ keybinding chức năng muốn dùng.
Ác mộng lúc bắt đầu dùng Emacs là 1 đống phím tắt cần nhớ. Tuy nhiên bạn không cần cố nhớ, hãy bắt đầu bằng vài thao tác cơ bản, cái nào không biết hãy google hoặc dùng chế độ help. Dùng vài hôm, tay sẽ nhớ thay cho não.

4. Why not VIM.
Giới lập trình viên vẫn cãi nhau chí choé Emacs & Vim, nên dùng thằng nào. Một kiểu như Windows & Linux, Android & Iphone.
Riêng với mình cũng dự định thử dùng Vim cho nghiêm túc xem sao, nhưng giờ chưa dùng vì chưa có thời gian để thử. Ai đang dùng Vim nhờ chỉ bảo chút 🙂

5.Tham khảo.
[1]http://labang.sourceforge.net/articles/emacs-tutorial-vi.html
[2]https://viblo.asia/p/my-friends-emacs-config-al5XRBlkGqPe
[3]https://astraybi.wordpress.com/2015/08/01/how-to-setup-gnu-global-for-emacs-mac/

 

Ẩn dấu dữ liệu bằng motion vector

1. Information Hiding, Digital Watermarking and Steganography
Đây là các khái niệm nói việc việc mã hóa, nhúng thông tin vào trong đối tượng (có thể là picture, audio, video …), có liên quan đến vấn đề bảo mật thông tin, hoặc bảo vệ bản quyền.
Đây là vấn đề rộng, chỉ đưa ra keyword chứ không dám lạm bàn, kẻo thành nói ngu, mà nói ngu thì cực kì nguy hiểm cho cả bản thân với ai đó chưa biết mà không may đọc phải.

2. Ẩn dấu dữ liệu bằng motion vector
Motion compensation (bù chuyển động) là thuộc loại công cụ nén quan trọng và mang về tỉ lệ nén rất cao trong việc nén video.
Với các ảnh liên tiếp nhau, về cơ bản là đối tượng trong ảnh phần lớn giống nhau (trừ tình huống chuyển cảnh), chỉ khác nhau do vị trí của nó bị xê dịch. Kỹ thuật bù chuyển động đưa ra nhằm tận dụng đặc trưng này của luồng video, thay vì lưu lại toàn bộ ảnh thì nó chỉ lưu lại phần chuyển động của bằng các vector, gọi là motion vector. Dĩ nhiên là cả điểm khác nhau sau khi đã bù chuyển động (DPCM), nhưng sự khác nhau này đã giảm đi đáng kể sau khi có tính đến bù chuyển động. Trong việc nén video, thì quá trình tìm kiếm vùng giống nhau giữa các ảnh sẽ quyết định motion vector tối ưu.
mv1

Vấn đề muốn đề cập ở đây, là ta có thể tận dụng chính bộ vector này để đồng thời mã hóa dữ liệu mong muốn trong quá trình nén video. Đầu tiên bộ encoder sẽ tìm motion vector tối ưu, trước khi đưa motion vector đó vào thực hiện nén video, thì chỉnh sửa nó một cách có chủ ý nhằm nhúng dữ liệu vào đấy, tất nhiên giá trị motion vector không còn tối ưu nữa, nên ảnh hưởng ít nhiều đến chất lượng video. Việc chỉnh sửa thế nào tuân theo 1 qui tắc nào đó mà chỉ có encoder, và tiết lộ cho bên decoder mà được phép nhận chia sẻ dữ liệu đó biết, bên phía decoder khi giải mã video đó thì dựa vào motion vector sẽ lấy được thông tin được ẩn dấu trong đó.
Để dễ hình dung có thể xem hình vẽ dưới.
hidedata1

3. Thử chơi với H.264
Áp dụng cho H.264, bằng cách chọn một codec có sẵn nào đó, sửa motion vector theo 1 qui luật nhất định.
Để dễ dàng thực hiện thì sử dụng codec hàn lâm nhất jvt codec[2], cái này là codec do JVT sử dụng trong quá trình xây dựng chuẩn nén H.264.
Nếu xem xét motion vector nhỏ hơn 1 giá trị ngưỡng nào đấy (threshold value) thì sẽ sửa nó về giá trị nhỏ hơn hoặc bằng sqrt(2), tức là motion vector nhỏ hơn giá trị ngưỡng luôn có giá trị trong tập (0,0); (0,1) ; (1,0) ; (0,-1);(1,1)… Bằng cách qui định dữ liệu ứng với với các giá trị này thì ta có thể mã hóa 1 chuỗi bit nhị phân bằng motion vector (tham khảo file insert_data.c trong source code[3]).
Sau khi chỉnh sửa jvt codec[2], bộ codec mới của H.264 ở [3] đã hỗ trợ việc sử dụng motion vector để nhúng dữ liệu vào ở bộ encoder, còn bộ decoder thì có thể giải mã nó và lưu thành 1 file có kết quả giống như dữ liệu đã nhúng.

h264_1
Thông tin và cách sử dụng có thể tham khảo readme.txt của source code[3], khi thay đổi data video đầu vào như frame size, số frame thực hiện mã hóa … thì cần sửa lại file cấu hình (config) của bộ encoder cho phù hợp.

Hạn chế: làm lâu rồi nên không nhớ, nhưng hình như khi nhúng data bằng cả motion vector trước và sau (backward & forward) thì có bug, ai đó fix hộ đi 😀 .

Tài liệu tham khảo
[1] https://en.wikipedia.org/wiki/Digital_watermarking
[2] http://iphome.hhi.de/suehring/tml
[3] https://github.com/truongpt/jm19.0_watermarking