มาดูขั้นตอนการบูทของ Raspberry Pi บ้าง…
Boot process
ขั้นตอนการบูตแบบสั้นๆ
Aqua Utopia|海の底で記憶を紡ぐ
$LAYYYTER
2025 on Tumblr: Trends That Defined the Year

roma★
Monterey Bay Aquarium
Xuebing Du
Game of Thrones Daily

@theartofmadeline

⁂

#extradirty

izzy's playlists!
cherry valley forever
Sade Olutola
KIROKAZE
DEAR READER

Kaledo Art
hello vonnie
TVSTRANGERTHINGS
Today's Document
Three Goblin Art

seen from India
seen from Chile
seen from Guatemala

seen from Brazil
seen from Uzbekistan
seen from Netherlands
seen from China
seen from United States

seen from Malaysia
seen from Singapore
seen from United States

seen from Bosnia & Herzegovina
seen from Russia

seen from China
seen from Philippines
seen from Pakistan

seen from United States
seen from United States
seen from France

seen from Colombia
@e22wiw-blog
มาดูขั้นตอนการบูทของ Raspberry Pi บ้าง…
Boot process
ขั้นตอนการบูตแบบสั้นๆ
โปรแกรมสำหรับ … copy framebuffer จาก /dev/fb0 ไป /dev/fb1 หรือเรียกง่ายๆว่า mirror/clone หน้าจอ
ที่มา
ใน Raspberry Pi สามารถที่จะนำจอ TFT ขนาดเล็กมาต่อทาง SPI/GPIO ได้ โดยเมื่อต่อเข้าไปก็ไจะได้ device ของจอมาเพิ่มอีกหนึ่งจอ คือ /dev/fb1 ซึ่งสามารถที่จะปรับแต่ง config เพื่อให้ใช้งาน Console,...
Raspberry Pi: Use I2C and Camera at the same time.
Just work around right now.
$ sudo -s $ echo bcm2708_i2c.0 > /sys/bus/platform/drivers/bcm2708_i2c/unbind
อะไรคือ RTOS และเมื่อไหร่ควรจะใช้ RTOS คำถามลักษณะนี้ บางครั้งก็ตอบยาก หรือบางทีตอบไปแล้วก็ไม่แน่ใจว่าผู้ฟังจะเข้าใจหรือเปล่า หรือผู้ตอบบางที่ก็งงๆว่าตัวเองจะเข้าใจถูกหรือเปล่า
Embedded System and Real Time System
Real Time System ตามนิยามที่ให้ๆกันไว้...
สำหรับใครที่อยากใช้ Raspberry Pi ไปใช้ทำพวก Media Player แล้วอยากใช้ Remote ทั่วๆไป มาใช้ควบคุม เราสามารถเพิ่ม IR Remote Receiver ให้กับ Raspberry Pi ในต้นทุน ไม่กี่ สิบบาท เท่านั้นเอง
How
ใน Linux มีซอฟต์แวร์สำหรับใช้รับและส่งสัญญาณ IR Remote มานานมากแล้ว โดยถ้าเป็นเครื่อง Linux PC...
Raspberry Pi: VideoCore #2, MMAL
ตอนที่แล้วได้กล่าวถึง มาตรฐานเปิดต่างๆที่ผู้ผลิต VideoCore ได้เตรียมไว้ให้ คราวนี้มาดู API อีกชุดที่ไม่ได้เป็นมาตรฐานเปิดบ้าง
MMAL (Multi-Media Abstraction Layer)
MMAL เป็นชุด API หรือ frameworks อีกชุดหนึ่งทีผู้ผลิต VideoCore ได้เตรียมไว้ให้ โดยภาพรวมๆแล้วก็จะคลายๆกับ OpenMAX IL ที่เคยพูดถึงในครั้งที่แล้ว แต่ถูกออกแบบมาเฉพาะให้ใช้งานได้ง่ายขึ้น โดยใช้กับ VideoCore โดยเฉพาะ
รายละเอียดเอกสารการใช้งานต่างๆเกี่ยวกับ MMAL ในขณะที่ผมเขียนอยู่นี้ หาได้น้อยมาก จะมีเพียงเอกสารที่อยู่ใน Comment และแกะจากการทำงานของตัวอย่าง Code เท่านั้น ซึ่งผมก็หวังเอาไว้ว่าทางผู้ผลิตคงจะทำเอกสารคู่มือออกมาให้มากขึ้นกว่านี้
การใช้งานการทำงานต่างๆของ MMAL นั้นคล้ายกับ OpenMAX IL มาก นั้นคือมีลักษาณะที่เป็น Component, Port, Connection และ Tunnel เหมือนๆกัน เหตุที่ผมยกเอา MMAL มาอธิบายให้ดูก็เพราะตัวอย่าง Code สำหรับถ่ายภาพนิ่งภาพวิดีโอด้วยกล้องของ Raspberry Pi (raspicam) นั้นเขียนโดยใช้ MMAL ไม่ใช่ OpenMAX IL ทำให้การศึกษา Code เพื่อทำความเข้าใจและนำไปเขียนโปรแกรมติดต่อกับกล้องด้วย MMAL ใน Raspberry Pi นั้นจะง่ายๆกว่า เพราะสามารถนำเอา Code หลายๆส่วน หรือวิธีการ Set ค่าต่างๆ ลำดับขั้นตอนการทำงาน จากตัวอย่าง Code ใน raspicam ไปใช้ได้มาก
Coding
เพื่อความง่ายในการทำความเข้าใจและทดลองเขียนโปรแกรม ผมได้ถอดเอา code จาก raspicam ใน userland ออกมาเขียนใหม่ และโดยลด code ให้น้อยที่สุดเท่าที่จะยังพอทำงานได้ แล้วได้ใส่ไว้ใน github ที่
https://github.com/tasanakorn/rpi-mmal-demo
ท่านที่สนใจสามารถที่จะ
$ git clone https://github.com/tasanakorn/rpi-mmal-demo.git $ cd rpi-mmal-demo $ mkdir build $ cmake .. $ make
โดยมีเงื่อนไขนิดหนึ่งคือ code ชุดนี้ผมยังไช้ include และ library จาก userland อยู่ ทำให้ต้องมี userland https://github.com/raspberrypi/userland อยู่ด้วย โดยให้วางไว้ที่ /home/pi/src/raspberrypi/userland (หรือจะวางที่อื่น แล้วแก้ CMakeLists.txt ก็ได้)
How it works
อย่างที่บอกครับ หลักการทำงานของ MMAL นั้นคล้ายกับ OpenMAX IL มาก ตัวอย่าง code ผมก็จะเขียนแบบเดียวกับครั้งที่แล้ว
include <stdio.h>
#include <stdlib.h> #include "bcm_host.h" #include "interface/vcos/vcos.h" #include "interface/mmal/mmal.h" #include "interface/mmal/util/mmal_default_components.h" #include "interface/mmal/util/mmal_connection.h" #define MMAL_CAMERA_PREVIEW_PORT 0 #define MMAL_CAMERA_VIDEO_PORT 1 #define MMAL_CAMERA_CAPTURE_PORT 2 int main(int argc, char** argv) { MMAL_COMPONENT_T *camera = 0; MMAL_COMPONENT_T *preview = 0; MMAL_ES_FORMAT_T *format; MMAL_STATUS_T status; MMAL_PORT_T *camera_preview_port = NULL, *camera_video_port = NULL, *camera_still_port = NULL; MMAL_PORT_T *preview_input_port = NULL; MMAL_CONNECTION_T *camera_preview_connection = 0; printf("Running...\n"); bcm_host_init(); status = mmal_component_create(MMAL_COMPONENT_DEFAULT_CAMERA, &camera); if (status != MMAL_SUCCESS) { printf("Error: create camera %x\n", status); return -1; } camera_preview_port = camera->output[MMAL_CAMERA_PREVIEW_PORT]; camera_video_port = camera->output[MMAL_CAMERA_VIDEO_PORT]; camera_still_port = camera->output[MMAL_CAMERA_CAPTURE_PORT]; { MMAL_PARAMETER_CAMERA_CONFIG_T cam_config = { { MMAL_PARAMETER_CAMERA_CONFIG, sizeof (cam_config)}, .max_stills_w = 1280, .max_stills_h = 720, .stills_yuv422 = 0, .one_shot_stills = 1, .max_preview_video_w = 1280, .max_preview_video_h = 720, .num_preview_video_frames = 3, .stills_capture_circular_buffer_height = 0, .fast_preview_resume = 0, .use_stc_timestamp = MMAL_PARAM_TIMESTAMP_MODE_RESET_STC }; mmal_port_parameter_set(camera->control, &cam_config.hdr); } format = camera_preview_port->format; format->encoding = MMAL_ENCODING_OPAQUE; format->encoding_variant = MMAL_ENCODING_I420; format->es->video.width = 1280; format->es->video.height = 720; format->es->video.crop.x = 0; format->es->video.crop.y = 0; format->es->video.crop.width = 1280; format->es->video.crop.height = 720; status = mmal_port_format_commit(camera_preview_port); if (status != MMAL_SUCCESS) { printf("Error: camera viewfinder format couldn't be set\n"); return -1; } status = mmal_component_enable(camera); status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &preview); if (status != MMAL_SUCCESS) { printf("Error: unable to create preview (%u)\n", status); return -1; } preview_input_port = preview->input[0]; { MMAL_DISPLAYREGION_T param; param.hdr.id = MMAL_PARAMETER_DISPLAYREGION; param.hdr.size = sizeof (MMAL_DISPLAYREGION_T); param.set = MMAL_DISPLAY_SET_LAYER; param.layer = 0; param.set |= MMAL_DISPLAY_SET_FULLSCREEN; param.fullscreen = 1; status = mmal_port_parameter_set(preview_input_port, ¶m.hdr); if (status != MMAL_SUCCESS && status != MMAL_ENOSYS) { printf("Error: unable to set preview port parameters (%u)\n", status); return -1; } } status = mmal_connection_create(&camera_preview_connection, camera_preview_port, preview_input_port, MMAL_CONNECTION_FLAG_TUNNELLING | MMAL_CONNECTION_FLAG_ALLOCATION_ON_INPUT); if (status != MMAL_SUCCESS) { printf("Error: unable to create connection (%u)\n", status); return -1; } status = mmal_connection_enable(camera_preview_connection); if (status != MMAL_SUCCESS) { printf("Error: unable to enable connection (%u)\n", status); return -1; } while (1); return 0; }
นั้นคือสร้าง component ที่เป็น camera และ video_render กำหนดค่า parameters ต่างๆ แล้วทำ tunnel port เข้าด้วยกัน ซึ่งผลการรันโปรแกรม ก็จะออกมา คล้ายๆ กับในตอนที่แล้ว (เพิ่มการปรับ Resolution ให้เป็นภาพ wide screen เข้ามา)
เมื่อเราเขาใจหลักการ การทำงานของเขียน MMAL และการทำงาน raspicam แล้ว เราก็จะประยุคดัดแปรงหรือเขียนโปรแกรมติดต่อกับกล้องหรือ component ต่างๆ ใน MMAL ได้
Raspberry Pi: VideoCore #1
Raspberry Pi เป็นคอมพิวเตอร์ขนาดเล็กที่หน่วยประมวลผลที่อยู่ใน Raspberry Pi ไม่ได้มีประสิทธิภาพในการประมวลผลทั่วๆไปที่สูงนัก แต่หน่วยประมวลผลของ Raspberry Pi ได้มีการใส่ความสามารถในการทำงานบางอย่างเฉพาะทางเพิ่มเข้าไป หนึ่งในความสามารถเฉพาะที่ Raspberry Pi มีคือกลุ่มทีเรียกว่า VideoCore
VideoCore
VideoCore เป็นหน่วยประมวลผลด้าน Graphics หรืออาจจะเรียกสั้นๆว่า GPU ที่มีความสามารถหลากหลาย โดยออกแบบมาให้สนับสนุน Application สำหรับผู้ใช้งานทั่วไป (Consumer) ที่มักจะใช้คอมพิวเตอร์เพื่อเล่น Game, ดูหนังฟังเพลงมากกว่าการประมวลด้านอื่น VideoCore ทีอยู่ใน Raspberry Pi จึงมึความสามารถที่จะใช้ฮาร์ดแวร์เฉพาะเพื่อทำการเข้ารหัส (Encoding) ถอดรหัส (Decoding) วิดีโอ, แสดงภาพ 3 มีิติ หรือแสดงภาพ Graphics ความเร็วสูง โดยผู้ผลิตหน่วยประมวลเตรียมให้ทำการเรียกใช้งานความสามารถเหล่านี้ผ่านทาง OpenMAX, OpenGL ES, OpenVG และ EGL ซึ่งเป็นมาตรฐานเปิดไว้ให้
OpenMAX : Multimedia Video, Audio
OpenGL : 3D Graphics
OpenVG : 2D Graphics
EGL : bridge between 3D & 2D
OpenMAX
OpenMax เป็น framework เพื่อใช้งานการเร่งความเร็ว (Acceleration) สำหรับงาน Video, Audio รวมทั้งภาพนิ่ง OpenMAX มีการแบ่งวิธีการเข้าใช้งานเป็น 3 ระดับ คือ Application Layer (AL), Integration Layer (IL) และ Development Layer (DL) ใน Raspberry Pi นั้นตัวอย่างที่ถูกในมาใช้อย่างชัดเจนจะอยู่ในระดับ Integration Layer
OpenMAX IL
ใน OpenMAX IL จะมีการโครงสร้างสถาปัติยกรรมที่เป็นลักษณะ Component และเชื่อมต่อเป็น Pipe Line คล้ายๆกับทีมีใน GStreamer จุดเด่นที่เพิ่มเข้ามาอย่างชัดเจนคือ OpenMAX IL มีการเชื่อม Pipe Line ในลักษณะ Tunnel ซึ่งเป็นการต่อท่อตรงระหว่าง Component บางคู่ที่สามารถที่จะเชื่อมต่อกันได้ ทำสามารถส่งต่อการชื่อสารระหว่าง Component ได้อย่างมีประสิทธิภาพ จุดเชื่อมต่อที่อยู่กับ Component แต่ละตัวก็จะเรียกว่า Port
ภาพจาก OpenMAX IL Specification
การใช้งาน OpenMAX IL ก็ทำได้โดยการสร้าง Component นั้นขึ้นมาแล้วทำการเรียกใช้ Port ต่างๆ ที่ Component นั้นมี หรือจะทำ Tunnel เชื่อม Port ระหว่าง Component ก็ได้
Component ใน OpenMAX IL จะมี Stage ในการทำงานอยู่หลาย Stage ซึ่งหลักๆดังนี้
LOADED : ยังไม่ถือ Resource
IDLE : จอง Resource เตรียมทำงาน
EXECUTING : ทำงาน
PAUSE : หยุดทำงานชั่วคราว สามารถทำงานต่อจากจุดที่หยุดได้
INVALIDE : เกิดปัญหา
WAIT_FOR_RESOURCE : รอ Resource
การสั่งให้ Component ต่างๆที่เราสร้างขึ้นก็ทำได้โดยการสั่งเปลี่ยน (Transition) Stage เหล่านี้เช่นกัน
Example 1: Camera and Video Render
เพื่อทำความเข้าใจให้ขึ้นลองมาดูตัวอย่าง Code ที่เรียกใช้ OpenMAX IL บ้าง ในตัวอย่างนี้จะเป็นการสร้าง Component สองตัวชื่อ camera และ video_render ขึ้นมา ทำการสร้าง tunnel เข้าด้วยกัน เพื่อนำเอาภาพจากกล้อง Camera Module ไปแสดงที่หน้าจอ
ใน camera จะประกอบไปด้วย port หลาย port ที่ทำงานแตกต่างกัน (รายละเอียดให้ดูจาก openmax il document ของ Raspberry Pi https://github.com/raspberrypi/firmware/tree/master/documentation/ilcomponents ) ในตอนนี้เราจะเชื่อม port ที่เป็น camera preview_port (70) ไปยัง video_render port (90)
ซึ่งก็จะเขียน Code อย่างๆง่ายๆ (โดยใช้ library บางส่วนที่อยู่ใน /opt/vc/src/hello_pi) ได้ดังนี้
#include #include #include #include "bcm_host.h" #include "ilclient.h" static int camera_test() { COMPONENT_T *video_camera = NULL, *video_render = NULL; COMPONENT_T * list[3]; TUNNEL_T tunnel[2]; ILCLIENT_T *client; int status = 0; memset(list, 0, sizeof (list)); memset(tunnel, 0, sizeof (tunnel)); if ((client = ilclient_init()) == NULL) { return -3; } if (OMX_Init() != OMX_ErrorNone) { ilclient_destroy(client); return -4; } // create camera if (ilclient_create_component(client, &video_camera, "camera", ILCLIENT_DISABLE_ALL_PORTS) != 0) status = -14; list[0] = video_camera; // create video_render if (status == 0 && ilclient_create_component(client, &video_render, "video_render", ILCLIENT_DISABLE_ALL_PORTS) != 0) status = -14; list[1] = video_render; set_tunnel(tunnel, video_camera, 70, video_render, 90); ilclient_change_component_state(video_camera, OMX_StateIdle); ilclient_setup_tunnel(tunnel, 0, 0); ilclient_change_component_state(video_camera, OMX_StateExecuting); ilclient_change_component_state(video_render, OMX_StateExecuting); while(1){} ilclient_disable_tunnel(tunnel); ilclient_teardown_tunnels(tunnel); ilclient_state_transition(list, OMX_StateIdle); ilclient_state_transition(list, OMX_StateLoaded); ilclient_cleanup_components(list); OMX_Deinit(); ilclient_destroy(client); return status; } int main(int argc, char **argv) { printf("Running... \n"); bcm_host_init(); return camera_test(); }
gist
จาก code ก็จะเป็น - การสร้าง component สองตัว ทำการ - เตรียมการสร้าง tunnel ระหว่าง camera port 70 ไปยัง video_render port 90 - เปลี่ยน Stage ไปเป็น Idle - สร้าง tunnel - เปลี่ยน Stage ไป Execute - ลูปรอ
และถ้าสิ้นสุดลูป ก็จะทำการ - ปิด tunnel - เปลี่ยน stage ไปเป็น idle (หยุดการทำงาน) และ loaded (คืน Resource) - clean up ต่างๆ
เมื่อรับโปรแกรมก็จะได้ output ที่หน้าจอหลักดังนี้
ใน code ตัวอย่างนี้เป็น code ที่เขียนเพื่อให้ทำความเข้าใจการทำงานได้ง่ายขึ้นเท่านั้น ยังขาดส่วนปรับแต่งค่าต่างๆ อีกหลายส่วน
RaspberryPi: GStreamer #2, Python binding
จากตอนที่แล้ว.. GStreamer เป็น Framework เพราะฉนั้นเราก็จะสามารถนำเอา GStreamer ไปสร้างเป็นแอพพลิเคชั่นได้ ซึ่งการพัฒนาซอฟต์แวร์ต่อยอดจาก GStreamer สามารถที่จะทำได้จากหลากหลายภาษา หนึ่งในภาษาที่พัฒนาได้ง่ายใน RaspberryPi และ GStreamer รองรับก็คือ Python
Pipe line
การใช้งาน GStreamer ในภาษาอื่นๆสามารถทำได้หลายแบบ เราสามารถที่จะสร้าง Element แต่ละ Element แล้วค่อยๆเชื่อมต่อให้ได้ Pipe Line ที่ต้องการก็ได้ แต่วิธีที่ง่ายวิธีหนึ่งคือ เราสามารถที่สร้าง Pipe Line จาก GStreamer Script แล้วค่อยทำการ parse เข้าสู่โปรแกรมเรา วิธีการนี้ทำให้เราสามารถที่จะลองทดสอบ Pipe Line ที่สร้างขึ้นด้วย gst-lanuch ได้ก่อน
ในคราวนี้เราจะมาสร้าง Pipe Line ที่มีการแสดง Text Overlay บนวิดีโออย่างง่ายๆ โดยเริ่มจากการสร้าง Pipe Line และทดสอบบน gst-launch ดันนี้
gst-launch -v videotestsrc ! textoverlay name=text1 font-desc="Sans bold 40" text="12345" valign=top ! videomixer ! ffmpegcolorspace ! fbdevsink
script ข้างบนเป็นการสร้าง Pipe Line ที่มี Text Overlay แล้วทำการ Mix กับวิดิโอ โดยเรามีแผนว่าเราจะทำการเปลี่ยน Text ที่ทำการ Overlay ด้วยโปรแกรม Python ดังนั้นเราจะทำการกำหนด name ของ Text Overlay ไว้ด้วย เพื่อให้ง่ายต่อการเชื่อมต่อ
Python binding
จากนั้นเราก็จะนำเอา Script ที่ได้ไปรวมกับโปรแกรมภาษา Python ที่เราเขียนขึ้น
#!/usr/bin/python import thread, time import glib, gobject import gst class CLI_Main: def __init__(self): pipeline_config = """ videotestsrc ! textoverlay name=text1 font-desc="Sans bold 40" text="12345" valign=top ! videomixer ! ffmpegcolorspace ! fbdevsink """ self.player = gst.parse_launch (pipeline_config) bus = self.player.get_bus() bus.add_signal_watch() bus.connect("message", self.on_message) def on_message(self, bus, message): t = message.type if t == gst.MESSAGE_EOS: self.player.set_state(gst.STATE_NULL) self.playmode = False elif t == gst.MESSAGE_ERROR: self.player.set_state(gst.STATE_NULL) err, debug = message.parse_error() print "Error: %s" % err, debug self.playmode = False def start(self): self.playmode = True self.player.set_state(gst.STATE_PLAYING) i = 0 out_text = "Waiting.." while self.playmode: i += 1 out_text = "Loop = %d" % (i) self.player.get_by_name("text1").set_property("text", out_text) time.sleep(0.1) loop.quit() mainclass = CLI_Main() thread.start_new_thread(mainclass.start, ()) gobject.threads_init() loop = glib.MainLoop() loop.run()
จากโปรแกรมข้างบนก็จะเป็นโปรแกรมภาษา Python ที่ทำการติดต่อกับ GStreamer ผ่านโมดูลชื่อ gst โดยโปรแกรมจะทำการ ออกแบบมาให้ทำงานเป็น Threading ที่ทำงานที่ function start ใน CLI_Main ส่วนในโปรแกรมหลักจะลูปรอไปเรื่อยๆ
ใน CLI_Main นั้น จะเห็นว่าเราก็จะทำเอา script ที่เคยทดลองกับ gst-launch มาใช้ใน gst.parse_launch ได้เลย แล้วใช้เรียกใช้ get_by_name และ set_property เพื่อเปลี่ยนค่า text ที่แสดงไปเรื่อยๆ
Finally
สำหรับการใช้ GStreamer บน RaspberryPi นั้น ยังมีข้อจำกัดในเรื่องของประสิทธิภาพอยู่ ซึ่ง RaspberryPi และ GStreamer ด้วยการเขียนโปรแกรมติดต่อโดยทั่วไปไม่สามารถที่จะทำงานกับวิดีโอความละเอียดสูงๆได้ โดยเฉพาะเมื่อต้องทำการประมวลผลที่มีความซับซ้อน ความละเอียดที่พอจะสามารถทำได้จะอยู่ที่ระดับ 320x240 - 640x480 ที่ Frame Rate ระดับ 15-20 fps เท่านั้น
ถ้าต้องการความละเอียดที่สูงกว่านี้จำเป็นที่จะต้องใช้วิธีดึงเอา VideoCore มาช่วยในการทำการประมวลผล โดยในขณะที่เขียนอยู่นี้ GStreamer (รุ่น 1.0) สามารถที่จะทำการติดต่อใช้งาน VideoCore โดยผ่าน OpenMAX ได้แล้ว ซึ่ง element ที่ติดต่อผ่าน OpenMax นี้จะมีคำว่า omx อยู่ในชื่อของ element เช่น omxh264enc, omxvc1dec, omxmjpegdec, omxh264dec, omxh263dec, omxmpeg4videodec, omxmpeg2videodec ซึ่งทำให้ประมวลผล GStreamer ได้เร็วขึ้นมาก แต่ก็ยังมีข้อจำกัดอยู่หลายประการ ตามความสามารถ OpenMAX ที่ผู้พัฒนาทำไว้ให้ เช่น ถ้าใช้ omxh264dec ทำการถอดรหัสไฟล์วิดีโอแล้วแสดงผลทันที่ผ่าน autovideosink จะสามารถแสดงผลระดับ 1080p ได้อย่างสมบูรณ์ แต่ถ้าไปทำการทำการประมวลผลทำ Textoverlay ก็จะยังได้ Frame Rate ที่ต่ำมากอยู่
__
RaspberryPi: GStreamer #1
Introduction to GStreamer on RaspberryPi
จากที่เคยเกริ่นไว้ ใน Project MSBAT3 ผมได้ใช้ GStreamer บน RaspberryPi เพื่อทำการซ้อนภาพวิดีโอและข้อมูลต่างๆก่อนทำการถ่ายทอดสัญญาณ ออกมาเป็นสัญญาณโทรทัศน์ ในตอนผมขอแนะนำการใช้งาน GStreamer เบื้องต้นบน RaspberryPi
What is GStreamer ?
GStreamer คำจำกัดความที่เคยให้ไว้ใน wiki ก็คือ "GStreamer is a pipeline-based multimedia framework" ก็คือเป็นชุดเฟรมเวิร์คเพื่อใช้ในการทำพวกมัลติมีเดีย GStreamer ทำให้เราสามารถที่จะทำการพัฒนาซอฟต์แวร์ด้านมัลติมีเดียงทางภาพนิ่งภาพเคลื่อนไหวและเสียงได้อย่างง่ายดาย ด้วยภาษาโปรแกรมหลากหลายภาษา แม้กระทั้ง shell script
Installation
GStreamer เป็น OpenSource ที่สามารถติดตั้งได้หลากหลาย Platform ไม่ว่าจะเป็น Linux, Windows หรือ OSX ซึ่งสามารถติดตั้งได้หลายหลายวิธีการ สำหรับใน RaspberryPi ถ้าใช้ Image ชื่อ Raspbian ซึ่ง Based จาก Debian สามารถที่จะติดตั้งได้โดยผ่านชุดคำสั่ง apt ดังนี้
$ sudo apt-get install gstreamer-tools gstreamer0.10-plugins-base gstreamer0.10-plugins-good gstreamer0.10-plugins-bad gstreamer0.10-x python-gst0.10 gstreamer0.10-ffmpeg
package ที่อยู่ใน Raspbian นั้นจะแบ่งเป็น Package หลายๆ Package ซึ่งเลือกลงได้ตามความต้องการ ตัวอย่างการติดตั้งที่ผมเสนอคือ Package หลักๆที่ผมใช้บ่อยๆ
Run GStreamer pipe-line with gst-launch
อย่างที่ผมบอกไว้ตอนต้นๆ GStreamer สามารถเรียกใช้ได้จากภาษาโปรแกรมหลากหลายภาษาแม้กระทั้งจาก Shell เราสามารถทดสอบหรือเรียกใช้งาน GStreamer ได้จากคำสั่ง gst-launch
Example : video test
ใน Project MSBAT3 ผมใช้ GStreamer เพื่อแสดงผลไปยัง Framebuffer โดยตรงโดยไม่ผ่าน X-Window ซึ่ง GStreamer เองก็สามารถเลือกที่จะแสดงผลไปยัง output ต่างๆได้หลากหลายแบบ ซึ่งโดยค่า Default จะแสดงผลไปยัง X-Window แต่เนื่องจากผมใช้ Framebuffer โดยตรง ตัวอย่างที่ผมแนะนำ ก็จะทำการสั่งให้ GStreamer แสดงที่ Framebuffer ด้วย
$ gst-launch -e -v videotestsrc ! fbdevsink
ตัวอย่างข้างบน เป็นการเรียกใช้ gst-launch ทำการรัน pipe-line ที่นำเอา videotestsrc ที่เป็น element สำหรับสร้างภาพวิดีโอทดสอบ ไปแสดงผลไปยัง fbdevsink ที่เป็น framebuffer ส่วนพารามิเตอร์ -e -v ใส่เพื่อให้แสดงข้อมูลการทำงานแบบ Verbose (-v) และทำการสั่ง End Of Stream (-e) ก่อนจะปิดโปรแกรม
element ที่อยู่ใน GStreamer ถ้าเป็นแหล่งกำเนิดต้นทางมักจะลงท้ายด้วย src (Source) ส่วนปลายทางจะลงท้ายด้วย sink การเชื่อมต่อระหว่าง element ก็จะ ขั้นด้วยเครื่องหมาย ! (เครื่องหมายอัศเจรีย์) ผลจากการรันก็จะได้ดังภาพ
ถ้าทำงานบน X-Window สามารถเปลี่ยนจาก fbdevsink เป็น ximagesink หรือใช้ autovideosink ก็ได้
Example : Play mp4
คราวนี้ลองมาสั่งเล่นไฟล์ Video อย่างง่ายๆดูบ้าง
$ gst-launch -e -v playbin2 uri='file:///home/pi/data/sample.mp4' video-sink='fbdevsink'
ใน GStreamer จะมี element ที่ใช้เล่นไฟล์หรือ Stream อย่างง่ายชื่อ playbin และ playbin2 (playbin เลิกใช้แล้ว) ซึ่งจะทำพยายามอ่าน Stream decode สร้าง pipe-line ที่จำเป็นให้จนแสดงผลภาพเสียงได้เลย (เนื่องจากผมใช้ framebuffer ต้องระบุ sink ไว้ ถ้าเป็น x-window ไม่ต้องระบุก็ได้)
Example : Play live stream
นอกจากไฟล์ แล้ว playbin2 ยังใช้เล่น Live video stream ได้ด้วย
$ gst-launch -e -v playbin2 uri='rtmp://122.155.18.162:1935/live/demo.sdp' video-sink='fbdevsink'
Example : Clock overlay
แต่ถ้าเราต้องการจะสร้าง pipe-line ที่ซับซ้อน อาจจะต้องทำอะไรยุ่งยากอีกหน่อย
$ gst-launch -e -v filesrc location=/home/pi/data/sample.mp4 ! decodebin2 name=demux demux. ! queue ! clockoverlay halignment=left valignment=top deltax=-20 deltay=15 shaded-background=true time-format="%Y-%m-%d %H:%M:%S" ! videomixer ! ffmpegcolorspace ! fbdevsink demux. ! queue ! autoaudiosink
ชุดคำสั่งข้างบนก็จะเป็นการสร้าง pipe-line ที่อ่านไฟล์ด้วย filesrc แล้วทำการถอดรหัสด้วย decodebin2 โดยกำหนดชื่อให้ว่า demux แล้วส่งไปทำ mix รวมกับ clockoverlay ด้วย videomixer อีกส่วนหนึ่งก็ส่งไปแสดงเสียงด้วย autoaudiosink
MSBAT-3: RaspberryPi
MSBAT-3 High Altitude Amateur Radio Balloon
โครงการ MSBAT-3 เป็นโครงการส่งบอลลูนทดลองด้านวิทยุสมัครเล่นที่บรรทุก Payload หลายอย่าง ไม่ว่าจะเป็น APRS, Sensor วัดอุณหภูมิ, ความกดอากาศ เครื่องส่ง CW ในย่าน HF 28Mhz, สถานีทวนสัญญาณ UHF/VHF และเครื่องส่งโทรทัศน์ VHF (~54Mhz)
ในโครงการนี้ ได้นำเอา RaspberryPi ซึ่งเป็นเครื่่อง Computer ขนาดเล็กมาใช้ในโครงการด้วย โดย RaspberryPi จะมีหน้าที่หลักสองอย่างใหญ่ๆ คือ
เป็น Data Logger ที่รับข้อมูลจากบอร์ด Sensor/Tracker มาเก็บบรรทึกไว้
นำข้อมูลพิกัด, Sensor ที่่รับมาและภาพจาก USB Camera มาซ้อนรวมกันส่งออกเป็นสัญญาณภาพเพื่อให้เครื่องส่งสัญญาณโทรทัศน์แพร่สัญญาณออกมา
โดยในส่วนที่การซ้อนภาพนี้จะได้พัฒนาซอฟ์ตแวร์ที่เขียนโดยภาษา Python ทำงานใน Console นำสัญญาณภาพจาก USB Camera ภาพนิ่ง และข้อความต่างๆมาซ้อนรวมกันโดยใช้ gstreamer แล้วทำการนำ output ที่ได้ส่งไปที่ Frame buffer โดยตรง
การที่เลือกทำงานใน Console และแสดงภาพไปยัง Frame Buffer โดยตรง มีจุดประสงค์หลายๆอย่าง หลักๆก็คือ
ลดระยะเวลาในการ Boot ระบบ
ไม่ใช้ x-windows เพื่อลดภาระของ CPU และเพ่ิมความเร็วในการประมวลผล
จัดการเรื่อง การ start/restart program, screen server ได้ง่ายขึ้น
ในเรื่อง การ start/restart program นั้นโปรแกรมที่พัฒนาขึ้น ได้ใช้ inittab ซึ่งเป็น service หลักของระบบ Linux เป็นตัว start/restart โปรแกรม โดยให้โปรแกรมทำงานทันทีเมื่อบูตระบบเสร็จและถ้าเกิดข้อผิดพลาดโปรแกรมหยุดทำงาน inittab ก็จะช่วย restart โปรแกรมด้วยเช่นกัน.
นอกจากนี้ การที่ใช้ Frame Buffer โดยตรง ทำให้สามารถแสดงภาพยังหน้าจอปัจจุบันได้ตลอดเวลา โดยไม่ถูกโปรแกรมอื่นอย่างเช่น screen server หรือ log แย่งทำงาน อีกด้วย
Short review: CP2102 breakout board
Use RaspberryPi without additional hardware to transmit CW radio signal.
My MSBAT-3 (High altitude amateur radio balloon) video (before lunch)
ATMega8 cw keyer
ATMega8 CW generator
CW: Calculating morse timing
เนื่องจาก Morse code นั้นในแต่ละตัวอักขระจะประกอบไปด้วยเสียงสั้นยาวที่แตกต่างกันทำให้ระยะเวลาของอักขระแต่ละตัวนั้นแตกต่างกันไปด้วย การวัดความเร็วของ Morse code จึงทำการวัดโดยกำหนดคำหนึ่งคำเพื่อใช้วัดความเร็ว ซึ่งคำที่ใช้ในการวัดความเร็วก็คือคำว่า PARIS โดยจะประกอบไปด้วยตัวอักขระ 5 อักขระมีการเว้นระหว่างตัวอักขระสี่ครั้งและเว้นระหว่างคำอีกหนึ่งครั้ง คำนวณความยาวได้เท่ากับ
11 + 3 + 5 + 3 + 7 + 3 + 3 + 3 + 5 + 7 = 50 หน่วยเวลา (ระยะห่างระหว่างอักขระ = 3, ระยะห่างระหว่างคำ = 7)
ความเร็ว n WPM หมายถึง ความเร็วที่สามารถส่งคำว่า PARIS จำนวน n ครั้งในระยเวลา 1 นาที ซึ่งสามารถคำนวนระยะเวลาต่อหนึ่งหน่วยเวลาได้จาก
t = 60 / (50 * WPM) t = 1.2 / WPM ... sec.
ตัวอย่างค่าของหนึ่งหน่วยเวลาใน ความเร็วต่างๆ
8WPM, t = 150.00ms 9WPM, t = 133.33ms 10WPM, t = 120.00ms 11WPM, t = 109.09ms 12WPM, t = 100.00ms 13WPM, t = 92.31ms 14WPM, t = 85.71ms 15WPM, t = 80.00ms 16WPM, t = 75.00ms 17WPM, t = 70.59ms 18WPM, t = 66.67ms 19WPM, t = 63.16ms 20WPM, t = 60.00ms 21WPM, t = 57.14ms 22WPM, t = 54.55ms 23WPM, t = 52.17ms 24WPM, t = 50.00ms 25WPM, t = 48.00ms 26WPM, t = 46.15ms 27WPM, t = 44.44ms 28WPM, t = 42.86ms 29WPM, t = 41.38ms 30WPM, t = 40.00ms
จากที่เสียงสั้นเสียงยาวนั้นโดยปรกติจะมีค่าระยะเวลา 1 และ 3 หน่วยเวลาตามลำดับซึ่งมีความแตกต่างมากพอทีจะทำให้ทราบได้ว่าเสียงใด้เป็นเสียงสั้นเสียงใด้เป็นเสียงยาว เช่นที่ความเร็ว 8 คำต่อนาที ความยาวเสียงสั้นก็จะเท่ากับ 0.150 วินาที ความยาวเสียงยาว ก็จะเท่ากับ 0.450 วินาที ในความเป็นจริงๆแล้ว คนเราคงจะไม่สามารถที่จะเคาะรหัสได้ความยาวถูกต้องตลอดเวลา ตัวเลขที่ผมเคยเห็นซอฟต์แวร์ใช้เป็นหลักพื้นฐานในการแยกแยะความยาวเสียงสั้นและเสียงยาวก็จะใช้ครึ่งทางคือแบ่งกันที่ 1.5 หน่วยเวลา นั้นคือทีความเร็ว 8 คำต่อนาที เราควรจะต้องเคาะเสียงสั้นยาวโดยให้ระยะเวลาผิดเพี้ยนไปไม่เกิน 0.075 วินาที (อย่างไรก็ดีคนเราทั่วไปที่ถูกฝึกให้รับรหัสมอร์สมาเป็นอย่างดีอาจจะสามารถแยกแยะเสียงสั้นยาวได้ถึงแม้มีความผิดเพี้ยนมากกว่านั้น)
จากที่กล่าวไปข้างต้นในแต่ละอักขระจะมีจำนวนเสียงสั้นยาวที่แตกต่างกัน และได้มีการใช้คำว่า PARIS เป็นคำอ้างอิงในการวัดความเร็วของการส่งรหัส ความยาวของเสียงสั้นในอักขระที่เป็นตัวอักษรภาษาอังกฤษนั้นจะประกอบไปด้วย 1-4 เสียงเท่านั้น แต่สำหรับตัวเลขแล้วจะประกอบไปด้วย 5 เสียงเสมอโดยตัวเลขที่มีค่าเวลาสั้นที่สุดคือเลข 1(9t) และยาวที่สุด 0 (19t) เมื่อนำไปประกอบเป็น หนึ่งกลุ่มตัวเลขจะได้เป็น
11111 = 9+3+9+3+9+3+9+3+9+7 = 64t (ยาวกว่า PARIS 28%) 00000 = 19+3+19+3+19+3+19+3+19+7 = 114t (ยาวกว่า PARIS 128%)
นั้นคือถ้าเป็นการส่งกลุ่มของตัวเลข ไม่ว่าจะประกอบไปด้วยตัวเลขอะไรถ้าส่งด้วยความเร็วตามการวัดความเร็วด้วยคำว่า PARIS จะต้องใช้เวลาในการส่งมากว่าการส่งคำว่า PARIS ไม่น้อยกว่า 28%
The Farnsworth Technique
ในการฝึกรหัสมอร์สนั้นได้มีการสำรวจและวิจัยและพบว่า การฝึกมอร์สด้วยความเร็วที่ต่ำมากๆ ทำให้ผู้ฝึกฝน ฝึกรับรหัสมอร์สโดยการนับจำนวนเสียงสั้นเสียงยาว ทำให้เมื่อต้องการพัฒนาสู่ความเร็วสูงจะติดอยู่ที่กำแพงความเร็วหนี่ง (ในเอกสาร ARRL บอกว่าที่ 13 WPM) อันเป็นความเร็วสูงสุดที่คนเราจะสามารถนับเสียงสั้นยาวได้ คำแนะนำของ ARRL จึงแนะนำให้ฝึกฝนรหัสมอร์สความความเร็วระดับที่ไม่สามารถที่จะนับเสียงสั้นยาวได้ เพื่อให้ฝึกจดจำจังหว่ะ (rhythm) ของเสียงแทน โดยใช้เทคนิคที่เรียกว่า Farnsworth โดยแนะนำว่าถ้าความเร็วที่ต่ำกว่า 18 WPM การรับส่งจะใช้ค่าเวลาเดี๋ยวกับการส่งด้วยความเร็ว 18 WPM จะไม่มีการเพิ่มระยะเวลาของเสียงสั้นยาวให้มากขึ้น แต่จะไปเพิ่มระยะระหว่างตัวอักขระและระยะห่างระหว่างคำแทน
อ้างอิง.
ARRL: Standard for Morse Timing Using The Farnsworth Technique
เมื่อหลายเดือนก่อน เกิดไปสะดุดตากระทู้ปักหมุดในเว็บไซต์ 100watts.com ซึ่งกล่างถึง SDR : Software Defined Radio ซึ่งเป็นเครื่องรับวิทยุชนิดหนึ่ง ที่การทำงานมุ่งเน้นที่จะทำโดย Software ทำให้ตัว Hardware มีขนาดเล็กส่วนประกอบน้อย แต่กลับมีความสามารถที่หลากหลาย
...