網頁

2016年12月19日 星期一

gphoto

# pacman -S gphoto2
$ bzip2 -d gphoto2-2.5.11.tar.bz2
$ tar xvf gphoto2-2.5.11.tar
$ bzip2 -d libphoto2-2.5.11.tar.bz2
$ tar xvf libphoto2-2.5.11.tar

$ gphoto2 --list-ports
$ gphoto2 --auto-detect
USB PTP Class Camera  usb:001,005
$ gphoto2 --capture-image
$ gphoto2 --capture-image-and-download --no-keep
$ gphoto2 --port usb:001,005 --reset
$ gphoto2 --list-cameras|grep EOS
$ gphoto2 --port usb:001,005 --summary
$ gphoto2 --port usb:001,005 --list-config
/main/actions/manualfocusdrive
/main/settings/datetime
/main/status/cameramodel
/main/status/lensname
/main/status/eosserialnumber
/main/imgsettings/iso
/main/capturesettings/continuousaf
/main/capturesettings/autoexposuremode
/main/capturesettings/aperture
/main/capturesettings/shutterspeed
/main/capturesettings/aeb
$ gphoto2 --port usb:001,005 --get-config datetime
$ gphoto2 --port usb:001,005 --set-config datetime=now
$ gphoto2 --port usb:001,005 --set-config-index shutterspeed=45

$ gphoto2 --auto-detect|grep "usb:"|awk {'print $4'}

2016年11月22日 星期二

Cascade Classifier Training

開啟命令提示字元

set PATH=D:\OpenCV\build_64\install\x64\vc12\bin;%PATH%
set PATH=D:\OpenCV\dep\tbb2017_20160916oss\bin\intel64\vc12;%PATH%
set PATH=D:\Qt\Qt5.7.0_64\5.7\msvc2013_64\bin;%PATH%

cd neg
dir/b>neg.txt
編輯 neg.txt 加入檔案路徑

標註 正樣本
使用 opencv_annotation.exe
"D:\OpenCV\build_64\install\x64\vc12\bin\opencv_annotation.exe" --annotations=20160615.txt --images=20160615\*.jpg
c: add rectangle to current image
n: save added rectangles and show next image
d: delete the last annotation made
<ESC>: exit program(無法使用,請參考下列修正方式)

編輯 opencv_annotation.cpp
case 27
  //destroyWindow(window_name);
  stop = true;
  break;
不然無法用 ESC 結束程式

產生 pos.vec
"D:\OpenCV\build_64\install\x64\vc12\bin\opencv_createsamples.exe" -info pos.txt -vec pos.vec -w 50 -h 25

產生 xml
mkdir xml
"D:\OpenCV\build_64\install\x64\vc12\bin\opencv_traincascade.exe" -data xml -vec pos.vec -bg neg.txt -numPos 150 -numNeg 20 -numStages 20 -maxFalseAlarmRate 0.45 -featureType LBP -w 50 -h 25

-numPos 每級分類器訓練時所用正樣本數目
-numNeg 每級分類器訓練時所用負樣本數目,可大於 -bg 指定的圖片數目
-featureType: HAAR
-featureType: LBP
-mode BASIC 直立
-mode ALL
-minHitRate 0.999 正樣本被判成正樣本的比例
-maxFalseAlarmRate 0.5 負樣本被判成正樣本的比例

錯誤 Required leaf false alarm rate achieved. Branch training terminated.
預設 -maxFalseAlarmRate 0.5

2016年11月7日 星期一

OpenCV statusbar & mouse position

症狀:setMouseCallback 回傳函式的滑鼠位置錯誤,狀態烈沒有顯示滑鼠位置,顏色資訊
對策:開啟 modules/highgui/src/window_QT.h 將下一行增加 virtual 宣告
    void icvmouseProcessing(QPointF pt, int cv_event, int flags);
    virtual void icvmouseProcessing(QPointF pt, int cv_event, int flags);


OpenCV release 版本無法執行

症狀:debug 版本正常,release 版本的程式無法執行,出現如下錯誤
This application failed to start because it could not find or load the Qt platform plugin "windows"
in "".
原因:因為 PATH 環境變數設定錯誤,使用到錯誤的 Qt library
對策:修正 Project Property Pages/Coniguration Properties/Debugging/Environment 的 PATH
PATH=%PATH%;D:\Qt\Qt5.7.0_64\5.7\msvc2013_64\bin;
PATH=D:\Qt\Qt5.7.0_64\5.7\msvc2013_64\bin;%PATH%


除錯路上拾遺

查詢載入 plugin 的過程
設定環境變數 QT_DEBUG_PLUGINS=1
下列兩個環境變數皆可設定 plugin 的路徑
QT_QPA_PLATFORM_PLUGIN_PATH=D:\Qt\Qt5.7.0_64\5.7\msvc2013_64\plugins
QT_PLUGIN_PATH=D:\Qt\Qt5.7.0_64\5.7\msvc2013_64\plugins

編譯 Qt source
1. 新版的 Qt 編譯不能使用 vs2013,要使用 Visual C++ Build Tools(Visual Studio 2015)
2. 編譯需要跳過一些模組
configure.bat -release -nomake examples -skip qtscript

2016年10月26日 星期三

findContours, drawContours

findContours
Non-zero pixels are treated as 1's. Zero pixels remain 0's, so the image is treated as binary.
0: 黑, 255(白)
以黑為背景,尋找灰白的前景邊界

hierarchy
the elements hierarchy[i][0] , hiearchy[i][1] , hiearchy[i][2] , and hiearchy[i][3] are set to 0-based indices in contours of the next and previous contours at the same hierarchical level, the first child contour and the parent contour, respectively.
0: 下一個
1: 上一個
2: 第一個兒子
3: 父親
If for the contour i there are no next, previous, parent, or nested contours, the corresponding elements of hierarchy[i] will be negative.
沒有就是 -1

RETR_EXTERNAL
It sets hierarchy[i][2]=hierarchy[i][3]=-1 for all the contours.

RETR_CCOMP
所有邊界分為兩層, 白的外圈, 和內圈
hierarchy[i][3]=-1 for all the contours.

drawContours


2016年10月25日 星期二

Build OpenCV v3

下載安裝 git for windows

下載安裝 TortoiseGit, 設定 git for windows 的位置

建立目錄 D:/OpenCV
Clone the repository to D:\OpenCV from https://github.com/Itseez/opencv.git

下載安裝 CMake

除了分別下載安裝 python35_32, python35_64, 也要下載 Source

安裝 Setuptools
下載 ez_setup.py 置於 D:\Python35 下
執行 D:\Python35>python ez_setup.py

Install Sphinx via the command:
D:\Python35\Scripts\easy_inatall.exe sphinx

安裝 NumPy
cd D:\Python35\Scripts
pip install numpy

用 VS2013 開啟 PCbuild/pcbuild.sln, 主要建立 python 專案
可以建立出32位元 PCbuild/win32/python35_d.lib並且拷貝至 Python35_32/libs 下
64位元 PCbuild/amd64/python35_d.lib並且拷貝至 Python35_64/libs 下

Download and install Miktex: http://miktex.org/.
At the fourth step make sure you select for the "Install missing packages" the Yes option.
Miktex 只使用 64位元

Download the Intel TBB library: https://www.threadingbuildingblocks.org/, 選擇 Windows 版本
Extract it.
Create a dep directory in your OpenCV folder.
And copy the extracted files here.

Do the same with the eigen files: eigen.tuxfamily.org

下載 CUDA Toolkit 8.0 選擇 Windows 版本

安裝32和64位元的 Qt5.7 for VS2013, 於不同目錄

開啟 CMake
Where is the source code: D:/OpenCV/opencv
Where to build the binaries: D:.OpenCV/build_64
按 Configure 選擇 Visual Studio 12 2013 Win64
按 Generate, 此時會產生錯誤, 需要設定下列目錄
EIGEN_INCLUDE_PATH=D:\OpenCV\dep\eigen
開啟 PYTHON3 設定正確的版本位置
PYTHON3_EXECUTABLE=D:/Python35_32/python.exe
PYTHON3_INCLUDE_DIR=D:/Python35_32/include
PYTHON3_LIBRARY=D:/Python35_32/libs/python35.lib
PYTHON3_LIBRARY_DEBUG=D:/Python35_32/libs/python35_d.lib
PYTHON3_NUMPY_INCLUDE_DIRS=D:/Python35_32/Lib/site-packages/numpy/core/include
PYTHON3_PACKAGES_PATH=D:/Python35_32/Lib/site-packages
開啟 WITH_QT, WITH_TBB, WITH_CUDA
按 Generate, 此時會產生錯誤, 需要設定下列目錄
QT_QMAKE_EXECUTABLE=D:/Qt/Qt5.7.0_64/5.7/msvc2013/bin/qmake.exe
Qt5Concurrent_DIR=D:/Qt/Qt5.7.0_64/5.7/msvc2013/lib/cmake/Qt5Concurrent
Qt5Core_DIR=D:/Qt/Qt5.7.0_64/5.7/msvc2013/lib/cmake/Qt5Core
Qt5Gui_DIR=D:/Qt/Qt5.7.0_64/5.7/msvc2013/lib/cmake/Qt5Gui
Qt5OpenGL_DIR=D:/Qt/Qt5.7.0_64/5.7/msvc2013/lib/cmake/Qt5OpenGL
Qt5Test_DIR=D:/Qt/Qt5.7.0_64/5.7/msvc2013/lib/cmake/Qt5Test
Qt5Widgets_DIR=D:/Qt/Qt5.7.0_64/5.7/msvc2013/lib/cmake/Qt5Widgets
TBB_INCLUDE_DIRS=D:/OpenCV/dep/tbb2017_20160916oss/include

開啟 CMake
Where is the source code: D:/OpenCV/opencv
Where to build the binaries: D:.OpenCV/build_32
按 Configure 選擇 Visual Studio 12 2013
按 Generate, 此時會產生錯誤, 需要設定下列目錄
EIGEN_INCLUDE_PATH=D:\OpenCV\dep\eigen
開啟 PYTHON3 設定正確的版本位置
開啟 WITH_QT, WITH_TBB
關閉 WITH_CUDA, 因為 CUDA 沒有 32 位元版本
按 Generate, 此時會產生錯誤, 需要設定下列目錄
QT_QMAKE_EXECUTABLE=D:/Qt/Qt5.7.0_32/5.7/msvc2013/bin/qmake.exe
Qt5Concurrent_DIR=D:/Qt/Qt5.7.0_32/5.7/msvc2013/lib/cmake/Qt5Concurrent
Qt5Core_DIR=D:/Qt/Qt5.7.0_32/5.7/msvc2013/lib/cmake/Qt5Core
Qt5Gui_DIR=D:/Qt/Qt5.7.0_32/5.7/msvc2013/lib/cmake/Qt5Gui
Qt5OpenGL_DIR=D:/Qt/Qt5.7.0_32/5.7/msvc2013/lib/cmake/Qt5OpenGL
Qt5Test_DIR=D:/Qt/Qt5.7.0_32/5.7/msvc2013/lib/cmake/Qt5Test
Qt5Widgets_DIR=D:/Qt/Qt5.7.0_32/5.7/msvc2013/lib/cmake/Qt5Widgets
TBB_INCLUDE_DIRS=D:/OpenCV/dep/tbb2017_20160916oss/include

用 VisualStudio2013 開啟 D:\OpenCV\Builds\OpenCV.sln
選擇 ALL_BUILD, Build Debug 和 Release
選擇 INSTALL, Build Debug 和 Release



2016年10月19日 星期三

OpenCV 之 waitKey 使用

waitKey 除了不能區分大小寫,最慘的是和中文輸入法打架
使用 ImmDisableIME(-1); 關閉輸入法

2016年10月13日 星期四

Google Driver Web Page

原先可以正常使用的 語言學習 網站,近期一直不穩,甚至完全不能使用
之後查明原因為 Google 要停止這個功能

網路上有人建議使用 GitHub,但試過後發現,GitHub 可能也不支援了

目前將網頁搬至 000webhost,免費網路空間,目前感覺很棒
雖然該網站曾經被駭客入侵,但是我想它會改善,何況我也只是放單純的網頁


Visual Studio Installer 64bit 安裝

用 Notepad.exe 開啟 SetupProject.vdproj
修改 TargetPlatform
3.0 : x86 32-bit platform
3.1 : x64 64-bit platform, AMD64 and EM64T instruction sets
3.2 : Itanium 64-bit platform

修改 Application Folder 之 Properties 的 DefaultLocation
[ProgramFilesFolder][Manufacturer]\[ProductName]
[ProgramFiles64Folder][Manufacturer]\[ProductName]

2016年9月29日 星期四

SVN 謎團

TortoiseSVN import 成功
SVN Checkout 失敗

錯誤訊息: zlib (uncompress): corrupt data: Decompression of svndiff data failed
每次 Checkout 失敗的位置都不一樣,只能取回部分檔案。

懷疑是網路問題,但關閉防火牆也沒有用

執行"TortoiseSVN/Clean up" 之後 "TortoiseSVN/Update to revision" 可以取回所有檔案

至 SVN伺服器 可以成功執行
svnadmin dump OldRepository>OldRepository.dmp
svnadmin create NewRepository
svnadmin load NewRepository <OldRepository.dmp
沒有錯誤,但是客戶端還是一樣的問題

懷疑是新 import 進去的檔案錯誤,想移除後再 import 一次
svndumpfilter exclude "vc/CrossMonitor.2016" < OldRepository.dmp > filtered.dmp
rm -rf test
svnadmin create test
svnadmin load test < filtered.dmp
客戶端不能 import,產生下列錯誤
Can't read file 'D:\Subversion\test\db\revs\0\246': End of file found

版本 0-245正常,256失敗
svnadmin dump OldRepository -r0:245>245.dmp
rm -rf test
svnadmin create test
svnadmin load test < 245.dmp
客戶端可以 import,但一樣不能 checkout

到另一台客戶端可以成功 Checkout

修改 MTU 從 1500 到 1400,也沒有用,命令如下
netsh interface ipv4 show subinterfaces
netsh interface ipv4 set subinterface Wi-Fi mtu=1400 store=persistent

最後乾脆建立一個空的 Repository,可以 import,也可以 checkout
甚至舊的 Repository 也可以 checkout 了,天啊!

想查也沒得查了!

2016年9月6日 星期二

SpeechSynthesisUtterance 無法說中文

之前參考 Speech Synthesis API 製作 語音學習站
發現其中 中文的發音 在部分電腦可以正常發音,有些則不行

之後參考 SpeechSynthesisUtteranceGetting Started with the Speech Synthesis API 修正


// Chrome loads voices asynchronously.
window.speechSynthesis.onvoiceschanged = function(e) {
  var synth = window.speechSynthesis;
  voices = synth.getVoices();
  var voice_1 = document.getElementById('voice_1');
  while (voice_1.options.length) {
    voice_1.remove(0);
  }
  var voice_2 = document.getElementById('voice_2');
  while (voice_2.options.length) {
    voice_2.remove(0);
  }
  voices.forEach(function(voice, i) {
  var opt1 = document.createElement("option");
  opt1.textContent = voice.name + ' (' + voice.lang + ")";
  opt1.setAttribute('data-lang', voice.lang);
  opt1.setAttribute('data-name', voice.name);
  if (voice.lang == "en-US") {
    opt1.selected = true;
  }
  voice_1.appendChild(opt1);
});


2016年8月26日 星期五

EeePC 之觸控板

# synclient // 列出所有參數
# synclient TapButton1=1 // 模擬滑鼠按右鍵
# synclient TapButton12=3 // 模擬滑鼠按左鍵,但是沒用
# synclient VertEdgeScroll=1 // 觸控板右側,模擬垂直捲動
# synclient HorizEdgeScroll=1 // 觸控板底部,模擬水平捲動

開啟 x-win 時載入設定
# vi /etc/X11/xorg.conf.d/70-synaptics.conf
Section "InputClass"
    Identifier "touchpad"
    Driver "synaptics"
    MatchIsTouchpad "on"
        Option "TapButton1" "1"
        Option "VertEdgeScroll" "on"
        Option "HorizEdgeScroll" "on"
EndSection

EeePC 之 SSD 優化

安裝偵測工具
# pacman -Ss sysstat
# pacman -S sysstat
# iotop -oPa
# iostat -d 3 3

查詢磁碟分割是否對齊,對齊返回0
# blockdev --getalignoff /dev/sda1
I/O Scheduler 使用 NOOP
# cat /sys/block/sda/queue/scheduler
noop deadline [cfq]
# echo noop > /sys/block/sda/queue/scheduler // 不能使用 vi 編輯
# cat /sys/block/sda/queue/scheduler
[noop] deadline cfq
設定開機使用 NOOP
# vi /boot/syslinux/syslinux.cfg
APPEND root=/dev/sda1 elevator=noop

關閉 journal
# tune2fs -O "^has_journal" /dev/sda1

/etc/fstab 說明
discard: 使用 TRIM 指令
noatime: Do not update inode access times on this filesystem (e.g, for faster access on the news spool to speed up news servers).
relatime: Update inode access times relative to modify or change time. Access time is only updated if the previous access time was earlier than the current modify or change time. (Similar to noatime, but doesn't break mutt or other applications that need to know if a file has been read since the last time it was modified.)
data=ordered: This is the default mode. All data is forced directly out to the main file system prior to its metadata being committed to the journal.
data=writeback: Data ordering is not preserved - data may be written into the main filesystem after its metadata has been committed to the journal.
journal_checksum: Enable checksumming of the journal transactions.
journal_async_commit: Commit block can be written to disk without waiting for descriptor blocks.

$ cat /etc/fstab
#
#
# /etc/fstab: static file system information
#
# <file system> <dir> <type> <options> <dump> <pass>
# /dev/sda1
#UUID=f46258e7-e1ef-4127-a93e-ed8962b14f90 /         ext4       rw,relatime,discard,data=ordered 0 1
#UUID=f46258e7-e1ef-4127-a93e-ed8962b14f90 /         ext4       rw,noatime,discard,data=ordered 0 1
UUID=f46258e7-e1ef-4127-a93e-ed8962b14f90 /         ext4       rw,relatime,discard,journal_checksum,journal_async_commit,data=ordered 0 1
#UUID=f46258e7-e1ef-4127-a93e-ed8962b14f90 /         ext4       rw,noatime,discard,data=writeback 0 1
#UUID=f46258e7-e1ef-4127-a93e-ed8962b14f90 /         ext4       rw,noatime,discard 0 1
tmpfs /tmp tmpfs size=1G,noatime,mode=1777 0 0

做了上述的動作,但其實也分不出那些有用,那些沒用

EeePC 701 之 conky 設定

[mark@ArchLinux ~]$ cat .conkyrc 
override_utf8_locale yes
# Output
########
TEXT
${font sans:bold:size=12}CPU ${cpubar cpu0}
${font sans:bold:size=12}RAM $membar
${font sans:bold:size=12}BAT $battery_bar
${font sans:bold:size=12}CPU Temp: $alignr${execi 10 sensors | grep ^"temp1:" | awk '{print $2}'}
${font sans:bold:size=12}Fan: $alignr${execi 10 sensors | grep ^"fan1:" | awk '{print $2}'}RPM

#${font sans:bold:size=12}SYSTEM ${hr 2}
#${font sans:normal:size=12}$sysname $kernel $alignr $machine
#Host:$alignr$nodename
#Uptime:$alignr$uptime
${font sans:bold:size=12}TOP PROCESSES ${hr 2}
${font sans:normal:size=12}CPU: ${top name 1}${alignr}${top cpu 1}%
${font sans:normal:size=12}IO: ${top_io name 1}${alignr}${top_io io_write 1}
${font sans:normal:size=12}Mem: ${top_mem name 1}${alignr}${top_mem mem 1}%

修改 Win 10 的時鐘

regedit
HEKY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentCersion/ImmersiveShell
UseWin32TrayClockExperience REG_DWORD 0x00000001(1)

2016年8月19日 星期五

Arch Linux on EeePC 701

進入 Arch Linux 官網,選擇 HTTP 方式下載映像檔(archlinux-2016.08.01-dual.iso)
使用 Rufus 製作可開機的  USB/SD Card
連上有線網路,安裝會由網路下載資料
選擇由 SD 卡進入安裝程序

分割硬碟,因為硬碟不大,使用單一分割
# cgdisk /dev/sda
引號很重要,失敗很多次才知道
# mkfs.ext4 -O "^64bit" /dev/sda1
# mount /dev/sda1 /mnt
 將台灣的伺服器移到前面
vi /etc/pacman.d/mirrorlist
## Taiwan
Server = http://archlinux.cs.nctu.edu.tw/$repo/os/$arch
Server = http://shadow.ind.ntou.edu.tw/archlinux/$repo/os/$arch
Server = http://ftp.tku.edu.tw/Linux/ArchLinux/$repo/os/$arch
Server = http://ftp.yzu.edu.tw/Linux/archlinux/$repo/os/$arch
# pacstrap /mnt base
# genfstab -p -U /mnt >> /mnt/etc/fstab
# arch-chroot /mnt
# passwd
# echo ArchLinux > /etc/hostname
開啟 DHCP service
# systemctl enable dhcpcd@enp3s0.service
# vi /etc/locale.gen
解開下列3項
en_US.UTF-8 UTF-8
zh_CN.UTF-8 UTF-8
zh_TW.UTF-8 UTF-8
# locale-gen
# ln -s /usr/share/zoneinfo/Asia/Taipei /etc/localtime
# date
開機程序有一些不同的選擇,我後來使用 syslinux,另外有 GRUB, LILO 等
# mkinitcpio -p linux
# pacman -S gptfdisk
# pacman -S syslinux
# syslinux-install_update -i -a -m
# vi /boot/syslinux/syslinux.cfg
改 APPEND root=/dev/sda3 rw
為 APPEND root=/dev/sda1 rw
# exit
# umount /mnt
# reboot

login by root若之前已經開啟 DHCP Service,就不需執行下列命令
# dhcpcd enp3s0

安裝觸控板# pacman -S xf86-input-synaptics

安裝音效
# pacman -S alsa-utils
# alsamixer
# aplay /usr/share/sounds/alsa/Front_Center.wav
# arecord -d 5 test-mic.wav
# aplay test-mic.wav

安裝顯示卡驅動
# pacman -S xf86-video-intel

安裝圖形介面 Openbox, LXDM, Tint2
# pacman -S wget
# pacman -S sudo
# wget http://web.dhjh.tc.edu.tw/~gzqbyr/obtint.zip
# pacman -S unzip
# unzip obtint.zip
vi ./botint
在執行 obtint 前,將 firefox 改成 chromium
# ./obtint

設定 NumLock# vi /etc/lxdm/lxdm.conf
numlock=0

安裝 Mount 工具# pacman -S udevil

安裝 firefox 之 flash (安裝 chrome 之 flash 會移除這個 plugin)
# pacman -S flashplugin

安裝 chrome,若以利用 obtint 安裝過,不須執行下列命令# pacman -S chromium
安裝 chrome 之 flash
# pacman -S binutils
# pacman -S fakeroot
https://aur.archlinux.org/packages/adobe-flashplugin/
下載 adobe-flashplugin.tar.gz
$ tar xvfz adobe-flashplugin.tar.gz
$ cd adobe-flashplugin
$ makepkg -s
# pacman -U adobe-flashplugin-22.0.0.192-1-i686.pkg.tar.xz
重開機後使用 chrome 開啟 chrome://plugins 使用 Adobe Flash Player

設定 x-win 之 Menu/Browser
# vi ~/.config/openbox/menu.xml 改
<item label="Browser"><action name="Execute">
<execute>chromium -disk-cache-dir="/tmp"</execute>
</action></item>
設定 x-win 之 shortcut key
# vi ~/.config/openbox/rc.xml 改
<keybind key="W-w">
  <action name="Execute">
    <command>chromium -disk-cache-dir="/tmp"</command>
    <startupnotify>
      <enabled>yes</enabled>
      <name>Web Browser</name>
    </startupnotify>
  <action>
</keybind>

安裝 Network Manager 圖形化網路管理
# pacman -S networkmanager
# pacman -S network-manager-applet
# pacman -S wireless_tools
# systemctl stop dhcpcd@enp3s0
# systemctl disable dhcpcd.service
# systemctl enable NetworkManager.service
# reboot

顯示電池狀態$ vi ~/.config/tint2/tint2rc
battery_hide = never

切換 consoleAlt+Fn
有 X-Win 時切換 consoleCtrl+Alt+Fn
切換中文輸入法
Ctrl+Space

螢幕解析度設定
# pacman -S xorg-xrandr
# xrandr
# xrandr --output LVDS1 --panning 800x480 --scale 1.4x1.4
# xrandr --output LVDS1 --mode 800x480 --fb 800x600 --panning 800x600
# xrandr --output LVDS1 --mode 800x480 --fb 800x600 --panning 800x600 --scale 1x1
scale 最好是 1x1,這樣才看得清楚
fb 是桌面,panning 是滑鼠座標

修改桌面資訊顯示
# vi ~/.conkyrc
${top_io name 1}${top_io io_write 1} // io 最高的 process
${top_mem name 1}${top_mem mem 1}% // 佔最多記憶體的 process

偵測 CPU溫度,風扇
# pacman -Ss lm-sensors
# pacman -Ss sensors
# pacman -S lm_sensors
# sensors-detect // 它會問你一堆問題,最好是回答預設值
# cat /etc/modules
# sensors
# vi ~/.conkyrc
override_utf8_locale yes // 沒有設定,°C前會有亂碼
CPU Temp:$alignr${execi 10 sensors | grep ^"temp1:" | awk '{print $2}'}
Fan:$alignr${execi 10 sensors | grep ^"fan1:" | awk '{print $2}'}RPM

風扇控制# sensors-detect // 會在 /etc/modules 內加入感測器模組
# pwmconfig // 會去偵測 pwm 控制器,和風扇的關係,並產生 /etc/fancontrol
模組的位置
/sys/class/hwmon/hwmon?/device 主裝置,如CPU,主機板,顯卡等
/sys/class/hwmon/hwmon?/device/pwm? 裝置內可用 pwm
/sys/class/hwmon/hwmon?/device/fan?_input 裝置內可用風扇
/etc/fancontrol 說明
FCTEMPS=hwmon0/device/pwm1=hwmon0/device/temp1_input // PWM 裝置對映到的溫度感測器
FCFANS=hwmon0/device/pwm1=hwmon0/device/fan1_input // PWM 裝置對映到的風扇轉速計
MINTEMP 低於此溫度,風扇使用最小速度轉動
MAXTEMP 高於此溫度,風扇使用最高速度轉動
MINSTART 高於此 PWM,風扇開始轉動,通常大於 MINSTOP
MINSTOP 低於此 PWM,風扇停止轉動
MINPWM 當溫度低於 MINTEMP,使用此 PWM 轉動
MAXPWM 當溫度高於 MAXTEMP,使用此 PWM 轉動
# systemctl enable fancontrol.service
原本由 pwmconfig 產生的 fancontrol 是可以運作
但偶而會因為開機模組載入次序產生問題
# systemctl status fancontrol.service
所以改用下列設定檔
# vi /etc/fancontrol
# Configuration file generated by pwmconfig, changes will be lost
INTERVAL=10
FCTEMPS= /sys/devices/platform/eeepc/hwmon/hwmon[[:print:]]*/pwm1=/sys/devices/virtual/hwmon/hwmon[[:print:]]*/temp1_input
FCFANS= /sys/devices/platform/eeepc/hwmon/hwmon[[:print:]]*/pwm1=/sys/devices/platform/eeepc/hwmon/hwmon[[:print:]]*/fan1_input
MINTEMP= /sys/devices/platform/eeepc/hwmon/hwmon[[:print:]]*/pwm1=30
MAXTEMP= /sys/devices/platform/eeepc/hwmon/hwmon[[:print:]]*/pwm1=55
MINSTART= /sys/devices/platform/eeepc/hwmon/hwmon[[:print:]]*/pwm1=150
MINSTOP= /sys/devices/platform/eeepc/hwmon/hwmon[[:print:]]*/pwm1=100
MINPWM= /sys/devices/platform/eeepc/hwmon/hwmon[[:print:]]*/pwm1=100
MAXPWM= /sys/devices/platform/eeepc/hwmon/hwmon[[:print:]]*/pwm1=255

解決開機時沒插網路線,等待 1min 34s
# systemctl status dhcpcd@enp3s0.service // 錯誤訊息
# systemd-analyze critical-chain // 開機載入模組的相依
若開機時有使用 enable dhcpcd@enp3s0.service
# vi /etc/systemd/system/dhcpcd@.service.d/timeout.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dhcpcd -w -q -t 10 %I
若開機時使用 systemctl enable NetworkManager.service
# vi /usr/lib/systemd/system/dhcpcd@.service
ExecStart=/usr/bin/dhcpcd -w -q -t 10 %I

使用 ramdisk
# findmnt --target /tmp // 查詢 /tmp 的實體位置
手動加入 ramdisk
# mkdir /tmp
# chmod 777 /tmp
# mount -t tmpfs -o size=1G tmpfs /tmp // 使用 1G 的記憶體
開機時自動加入 ramdisk
# vi /etc/fstab // add a line
tmpfs /tmp tmpfs size=1G, mode=1777 0 0
chrome 使用 ramdisk
$ chromium --disk-cache-dir="/tmp/chrome"
# vi /usr/share/applications/chromium.desktop
Exec=/usr/bin/chromium -disk-cache-dir="/tmp/chrome" %U


下面是安裝時所遇到的一些困難過程

安裝 SD 開機時顯示
:: mounting '/dev/disk/by-label/ARCH_201608' to '/run/archiso/bootmnt'
Waiting 30 seconds for device /dev/disk/by-label/ARCH_201608 ...
ERROR '/dev/disk/by-label/ARCH_201608' device did not show up after 30 seconds...
You can try to fix the problem manually, log out when you are finished
我使用下列命令去修正它,但後來幾次就沒遇到,懷疑是接上有線網路,一切皆會順暢
ln -s /dev/sdb1 /dev/disk/by-label/ARCH_201608
mount /dev/sdb1 /run/archiso/bootmnt
rm /dev/disk/by-label/ARCH_201608
exit

使用 cgdisk 不用選擇開機磁區,試過下列命令
lsblk // 列出可用的儲存裝置
cgdisk /dev/sda
cfdisk /dev/sda
fdisk
spfdisk
parted /dev/sda print
parted /dev/sda set 1 bios_grub on
parted /dev/sda set 1 legacy_boot on
parted /dev/sda set 1 boot on
mkfs.ext4 /dev/sda1
mkfs.ext4 -O "^64bit" /dev/sda1
mkfs.ext4 -O "^has_journal" /dev/sda1

安裝網路時,試過下列命令
lspci -v // 查詢 PCI 裝置
Kernel moudles: ath5k
Kernel moudles: atl2
dmesg | grep ath5k // 在開機訊息中尋找,網路裝置名稱
ip link
iw dev
iw dev wlp1s0 link
ip link set wlp1s0 up
ip link show wlp1s0
iw dev wlp1s0 scan
iw dev wlp1s0 connect "your_essid"
iw dev wlp1s0 link
ip addr add 192.168.1.73/24 dev wlp1s0
ip route add default via 192.168.1.1
dhcpcd enp3s0
ping 192.168.1.1
vi /etc/resolv.conf // 設定 DNS
nameserver 192.168.1.1
ping www.google.com

設定安裝時,網路下載不穩定
vi /etc/pacman.conf // 使用 wget 下載,但使用有線網路時,一切順暢,不需要
#XferCommand /usr/bin/wget --passive-ftp -c -O %o %u

開機程序有一些不同的選擇,我後來使用 syslinux

查詢磁碟分割是否對齊,對齊返回0
# blockdev --getalignoff /dev/sda1
使用 TRIM 指令,加入 discard 旗標
取消檔案 atime 的修改,加入 noatime 旗標
vi /etc/fstab
/dev/sda1 / ext4 defaults,noatime,discard 0 1
I/O Scheduler 使用 NOOP
cat /sys/block/sda/queue/scheduler
noop deadline [cfq]
# vi /boot/syslinux/syslinux.cfg
APPEND root=/dev/sda1 elevator=noop
關閉 journal
tune2fs -O "^has_journal" /dev/sda1
查詢寫入最多的 process
iotop -oPa

2016年6月13日 星期一

c++ 上的 http json...

為了在 C 上使用 HTTP, JSON
挑上了 C++ REST SDK
看了範例,讓我如入五里迷霧
之前的 JSP, Java, Javascript 實在太簡單了

幸好看到了好文章 遇见C++ Lambda

C++ REST SDK 範例 JSON Server & Client
Full-fledged client-server example with C++ REST SDK 1.1.0

2016年6月3日 星期五

安裝 OpenCV 應用程式紀錄

32位元和64位元要分開使用 InstallShield

InstallShield Limited Edition Project

Project Assistant/Application Files
按 Add Project Outputs,勾選 Primary Output
64位元安裝要領
在 Destination Computer 上按滑鼠右鍵選 Show Predefined Folder/ProgramFiles64Folder
再將 ProgramFilesFolder 下的資料拖到 ProgramFiles64Folder

Specify Application Data/Redistributables
32位元勾選
Visual C++ 12.0 MFC (x86)
Visual C++ 12.0 CRT (x86)
64位元勾選
Visual C++ 12.0 MFC (x64)
Visual C++ 12.0 CRT (x64)

Configure the Target System/Shortcuts/folders
新增捷徑,注意在專案中增加一個 ico 檔
在捷徑的選項內選擇該檔案

除了安裝主程式外
有用vlc3.0, 所以要複製
OpenCV 的 build/install
OpenCV 相依(dep)的 tbb, qt5
下載安裝 CUDA Toolkit

PATH 環境變數加上下列路徑
C:\VLC-3.0.0\vlc-3.0.0-win64
C:\OpenCV\build_64\install\x64\vc12\bin
C:\OpenCV\dep\qt5_64\qtbase\bin
C:\OpenCV\dep\tbb43_20140724oss\bin\intel64\vc12
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v7.5\bin

2016年5月17日 星期二

轉換 rtsp 到另一個串流 rstp, 含 vlc-log.txt

const char *const vlc_args[] = {
"-I=dummy",
"--vout=dummy",
"--network-caching=5000",
"--sout=#rtp{sdp=rtsp://:8554/ch1}", "--sout-all", "--sout-keep",
"--verbose=2",
"--file-logging", logFile,
};
int vlc_argc = sizeof(vlc_args) / sizeof(vlc_args[0]);
vlcInstance = libvlc_new(vlc_argc, vlc_args);
const char *psz_mrl = "rtsp://169.254.1.168:554/live2.sdp";
media = libvlc_media_new_location(vlcInstance, psz_mrl);
mp = libvlc_media_player_new_from_media(media);
//libvlc_media_player_set_hwnd(mp, GetDlgItem(IDC_VIEWER)->m_hWnd);

vlc-log.txt

使用 avcodec 之 av_log

vi ../modules/codec/avcodec/video.c
static decoder_t *pAvDecoder = NULL;
static void AvLogOutCallback(void *ptr, int level, const char *fmt, va_list vl)
{
    if (level > av_log_get_level()) {
        return;
    }
/*
    FILE *fp = fopen("AvLogOut.txt", "a+");
    if (fp) {
        vfprintf(fp, fmt, vl);
        fflush(fp);
        fclose(fp);
    }
*/
    if (pAvDecoder != NULL) {
        va_list vl2;
        va_copy(vl2, vl);
        int msg_len = vsnprintf(NULL, 0, fmt, vl2);
        va_end(vl2);
        if (msg_len <= 0) return;
        char *msg = malloc(msg_len + 1 + 1);
        if (!msg) return;
        msg_len = vsnprintf(msg, msg_len+1, fmt, vl);
        if (msg_len > 0) {
            msg_Dbg(pAvDecoder, "av_log: %s", msg);
        }
        free(msg);
    }
}
static int OpenVideoCodec( decoder_t *p_dec )
{
    av_log_set_callback(AvLogOutCallback);
    vlc_init_avutil(p_dec);
}

使用硬體解碼,顯示時間

const char *const vlc_args[] = {
"-I=dummy",
"--vout=dummy",
"--network-caching=5000",
"--sout=#transcode{avcodec-hw=dxva2,acodec=g711,vcodec=h264,soverlay,sfilter=marq{marquee=%Y/%m/%d %H:%M:%S.%l,x=10,y=50}}:rtp{sdp=rtsp://:8554/ch1}", "--sout-all", "--sout-keep",
"--verbose=2",
"--file-logging", logFile,
};
int vlc_argc = sizeof(vlc_args) / sizeof(vlc_args[0]);
vlcInstance = libvlc_new(vlc_argc, vlc_args);
const char *psz_mrl = "rtsp://169.254.1.168:554/live2.sdp";
media = libvlc_media_new_location(vlcInstance, psz_mrl);
mp = libvlc_media_player_new_from_media(media);

libvlc_media_release(media);
libvlc_media_player_play(mp);


vi ../modules/stream_out/transcode/transcode.c 
#define HW_TEXT N_("Hardware decoding")
#define HW_LONGTEXT N_("This allows hardware decoding when available.")
    add_string( SOUT_CFG_PREFIX "avcodec-hw", NULL, HW_TEXT,
              HW_LONGTEXT, true )
static const char *const ppsz_sout_options[] = {
    "venc", "vcodec", "vb",
    "scale", "fps", "width", "height", "vfilter", "deinterlace",
    "deinterlace-module", "threads", "aenc", "acodec", "ab", "alang",
    "afilter", "samplerate", "channels", "senc", "scodec", "soverlay",
    "sfilter", "osd", "high-priority", "maxwidth", "maxheight",
    "avcodec-hw", NULL
};
static int Open( vlc_object_t *p_this )
{
    config_ChainParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options,
                   p_stream->p_cfg );

    char *pAvcodecHw = var_GetString(p_stream, SOUT_CFG_PREFIX"avcodec-hw");
    msg_Dbg(p_stream, "transcode.Open avcodec-hw = %s NNN", pAvcodecHw);
    if (pAvcodecHw) {
        var_Create(p_stream, "avcodec-hw", VLC_VAR_STRING);
        var_SetString(p_stream, "avcodec-hw", pAvcodecHw);
    }
}

duplicate sout 到 display, 並使用硬體解碼

const char *const vlc_args[] = {
"-I=dummy",
"--vout=dummy",
"--network-caching=5000",
"--sout=#duplicate{dst=display{avcodec-hw=dxva2},dst=rtp{sdp=rtsp://:8554/ch1}}", "--sout-all", "--sout-keep",
"--verbose=2",
"--file-logging", logFile,
};
int vlc_argc = sizeof(vlc_args) / sizeof(vlc_args[0]);
vlcInstance = libvlc_new(vlc_argc, vlc_args);
const char *psz_mrl = "rtsp://169.254.1.168:554/live2.sdp";
media = libvlc_media_new_location(vlcInstance, psz_mrl);
mp = libvlc_media_player_new_from_media(media);

libvlc_media_release(media);
libvlc_media_player_play(mp);


vi ../modules/stream_out/display.c 
#define HW_TEXT N_("Hardware decoding")
#define HW_LONGTEXT N_("This allows hardware decoding when available.")
    add_string( SOUT_CFG_PREFIX "avcodec-hw", NULL, HW_TEXT,
              HW_LONGTEXT, true )
static const char *const ppsz_sout_options[] = {
    "audio", "video", "delay", "avcodec-hw", NULL
};
static int Open( vlc_object_t *p_this )
{
    config_ChainParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options,
                   p_stream->p_cfg );

    char *pAvcodecHw = var_GetString(p_stream, SOUT_CFG_PREFIX"avcodec-hw");
    if (pAvcodecHw) {
        var_Create(p_stream, "avcodec-hw", VLC_VAR_STRING);
        var_SetString(p_stream, "avcodec-hw", pAvcodecHw);
    }
}

使用硬體解碼撥放

const char *const vlc_args[] = {
"-I=dummy",
"--vout=dummy",
"--network-caching=5000",
"--verbose=2",
"--file-logging", logFile,
};
int vlc_argc = sizeof(vlc_args) / sizeof(vlc_args[0]);
vlcInstance = libvlc_new(vlc_argc, vlc_args);
const char *psz_mrl = "rtsp://169.254.1.168:554/live2.sdp";
media = libvlc_media_new_location(vlcInstance, psz_mrl);
libvlc_media_add_option(media, ":avcodec-hw=dxva2");
mp = libvlc_media_player_new_from_media(media);
libvlc_media_player_set_hwnd(mp, GetDlgItem(IDC_VIEWER)->m_hWnd);

libvlc_media_release(media);
libvlc_media_player_play(mp);


snapshot from sout

const char *const vlc_args[] = {
"-I=dummy",
"--vout=dummy",
"--network-caching=5000",
  "--sout=#duplicate{dst=display,dst=rtp{sdp=rtsp://:8554/ch1}}", "--sout-all", "--sout-keep",  "--verbose=2",
"--file-logging", logFile,
};
int vlc_argc = sizeof(vlc_args) / sizeof(vlc_args[0]);
vlcInstance = libvlc_new(vlc_argc, vlc_args);
const char *psz_mrl = "rtsp://169.254.1.168:554/live2.sdp";
media = libvlc_media_new_location(vlcInstance, psz_mrl);
mp = libvlc_media_player_new_from_media(media);
libvlc_media_release(media);
libvlc_media_player_play(mp);

snapshot 需要 input_resource_t, 所以重點是傳遞 input_resource_t 的位址
vi ../modules/stream_out/display.c
    p_sys->p_resource = input_resource_New( p_this );
    var_Create(p_stream, "sout-display-input-resource", VLC_VAR_ADDRESS);
    var_SetAddress(p_stream, "sout-display-input-resource", p_sys->p_resource);
    msg_Dbg(p_stream, "display.Open set input_resource_t(%p) to sout_stream_t SSS", p_sys->p_resource);

將 input_resource_t 的位址,從 sout_stream_t 傳到 sout_instance_t
vi ../src/stream_output/stream_output.c
static sout_stream_t *sout_StreamNew( sout_instance_t *p_sout, char *psz_name,
                               config_chain_t *p_cfg, sout_stream_t *p_next)
{
    p_stream->p_module =
        module_need( p_stream, "sout stream", p_stream->psz_name, true );
    input_resource_t *p_resource = var_GetAddress(p_stream, "sout-display-input-resource");
    if (p_resource != NULL) {
        var_Create(p_sout, "sout-display-input-resource", VLC_VAR_ADDRESS);
        var_SetAddress(p_sout, "sout-display-input-resource", p_resource);
        msg_Dbg(p_sout, "stream_output:sout_StreamNew set input_resource_t(%p) from sout_stream_t to sout_instance_t SSS", p_resource);
    }
}

將 input_resource_t 的位址,從 sout_instance_t 傳到 input_thread_t
vi ../src/input/input.c
static int Init( input_thread_t * p_input )
{
    if (p_input->p->p_sout) {
        input_resource_t *p_resource = var_GetAddress(p_input->p->p_sout, "sout-display-input-resource");
        if (p_resource != NULL) {
            var_Create(p_input, "sout-display-input-resource", VLC_VAR_ADDRESS);
            var_SetAddress(p_input, "sout-display-input-resource", p_resource);
            msg_Dbg(p_input, "input.Init send input_resource_t(%p) from input_thread_t->p->p_sout to input_thread_t SSS", p_resource);
        }
    }
}

snapshot 是利用 vout_thread_t
vi ../lib/video.c
int libvlc_video_take_snapshot( libvlc_media_player_t *p_mi, unsigned num,
                            const char *psz_filepath,
                            unsigned int i_width, unsigned int i_height )
{
    vout_thread_t *p_vout = GetVout (p_mi, num);
}
GetVout -> GetVouts -> input_Control(INPUT_GET_VOUTS) -> input_vaControl

利用 input_resource_HoldVouts, 取得 vout_thread_t
vi ../src/input/control.c
int input_vaControl( input_thread_t *p_input, int i_query, va_list args )
{
        case INPUT_GET_VOUTS:
        {
            vout_thread_t ***ppp_vout = (vout_thread_t***)va_arg( args, vout_thread_t*** );
            size_t        *pi_vout = va_arg( args, size_t * );

            input_resource_HoldVouts( p_input->p->p_resource, ppp_vout, pi_vout );
            if( *pi_vout <= 0 ) { // 找不到,更換另一個 input_resource_t
                input_resource_t *p_resource = var_GetAddress(p_input, "sout-display-input-resource");
                msg_Dbg(p_input, "control.input_vaControl input_resource_HoldVouts(sout-display-input-resource) SSS");
                input_resource_HoldVouts(p_resource, ppp_vout, pi_vout);
            }
            if( *pi_vout <= 0 ) {
                return VLC_EGENERIC;
            }
            return VLC_SUCCESS;
        }
}

2016年5月2日 星期一

顯示 rtsp, 含 vlc-log.txt

CStringA logFile = "--logfile=vlc-log-" + GetTimeString() + ".txt";
const char *const vlc_args[] = {
"-I=dummy",
"--network-caching=5000",
"--sub-source=marq{marquee=%Y/%m/%d %H:%M:%S.%l,x=10,y=50}",
"--verbose=2",
"--file-logging", logFile,
};
vlcInstance = libvlc_new(vlc_argc, vlc_args);
const char *psz_mrl = "rtsp://169.254.1.168:554/live2.sdp";
media = libvlc_media_new_location(vlcInstance, psz_mrl);
mp = libvlc_media_player_new_from_media(media);
libvlc_media_player_set_hwnd(mp, GetDlgItem(IDC_VIEWER)->m_hWnd);

libvlc_media_release(media);
libvlc_media_player_play(mp);

vlc-log.txt

2016年4月14日 星期四

VLC 之 VLCLIB 新增錄影功能

以下參考 https://patches.videolan.org/patch/606/

vi include/vlc/libvlc_events.h
enum libvlc_event_e {
    libvlc_MediaPlayerMediaChanged=0x100,
    libvlc_MediaPlayerRecordableChanged,
    libvlc_MediaPlayerRecordingFinished,

    libvlc_MediaListItemAdded=0x200,
};
typedef struct libvlc_event_t
{
    union
    {
        struct
        {
            int new_recordable;
        } media_player_recordable_changed;
        struct
        {
            char *psz_filename;
        } media_player_recording_finished;
    } u;
} libvlc_event_t;

vi include/vlc/libvlc_media_player.h
/** @} audio */

/**
 * Can the media player record the current media?
 *
 * Media must be buffering or playing before it can be recorded.
 *
 * The media player event manager will emit a libvlc_MediaPlayerRecordableChanged event
 * when the recordable state changes after starting media playback. The event data will
 * describe the new recordable state, so invocation of this API method is not strictly
 * necessary to determine when recording can be started.
 *
 * A libvlc_MediaPlayerRecordableChanged event will not be emitted if the media is
 * stopped (notified by a libvlc_MediaPlayerStoppedEvent) or finishes normally (notified
 * by a libvlc_MediaPlayerFinished event).
 *
 * A calling application should therefore register an event callback for those events
 * so that it may query the new recordable state and manage recording at the appropriate
 * time.
 *
 * \param p_mi media player
 * \return true if the media player can record, false if it can not
 * \version LibVLC 2.1.0 or later
 */
LIBVLC_API bool libvlc_media_player_is_recordable( libvlc_media_player_t *p_mi );

/**
 * Is the current media being recorded?
 *
 * \param p_mi media player
 * \return true if recording, false if not
 * \version LibVLC 2.1.0 or later
 */
LIBVLC_API bool libvlc_media_player_is_recording( libvlc_media_player_t *p_mi );

/**
 * Start recording the current media.
 *
 * Media must be buffering or playing before it can be recorded. A calling application
 * can begin recording immediately on receipt of a libvlc_MediaPlayerRecordableChanged
 * event sent via the media player event manager (if recording is possible for the
 * currently playing media), and any time thereafter until the media stops.
 *
 * Media will be saved to the file path denoted by the psz_filename parameter if it is
 * supplied. Any such supplied filename should not include a file extension as the
 * correct file extension will automatically be appended when the file is created. This
 * filename may denote a full path name, but each directory in the path must already
 * exist or recording will silently fail. If the calling application chooses to specify
 * the filename then it is the responsibility of that application to take account of
 * this and itself make sure any needed directories are created.
 *
 * Alternatively, a calling application need not supply a filename and so instead let
 * vlc automatically generate a unique filename. This will cause vlc to create a new
 * file in the appropriate media directory for the user - for example "~/Videos". The
 * actual filename used will be sent in an event when the recording is complete.
 *
 * When recording has finished and the new file has been completely saved, a
 * libvlc_MediaPlayerRecordingFinished event will be sent via the media player event
 * manager. The event data will contain the filename of the newly recorded file - this
 * will either be the filename as specified by the calling application or a filename
 * generated by vlc if the application did not supply a filename. In either case, this
 * filename will include the automatically appended file extension.
 *
 * The saved media file will not be immediately available or visible until recording
 * has completely finished and the libvlc_MediaPlayerRecordingFinished event has been
 * received, or the media has stopped or finished normally.
 *
 * Recording can be stopped and started on-the-fly once the recordable state is set;
 * each time recording is stopped and restarted a new file will be created so a calling
 * application should take care to provide unique filenames, or defer to vlc to create
 * unique filenames.
 *
 * Recording will be stopped when the media stops playing, and must be explicitly
 * started again to restart recording, i.e. the recording state is not automatically
 * preserved when playing media subsequently.
 *
 * Media player functionailty such as next/previous chapter, set time or position and
 * so on are ineffective when recording is enabled. However, pausing the media is
 * possible and will pause the recording; unpausing the media will resume playback and
 * recording.
 *
 * Recording of the primary media or sub-items is possible.
 *
 * \param p_mi media player
 * \param psz_filename name of the file to save the media to, not including any file extension,
 *                     or NULL if vlc should generate the filename automatically
 * \return 0 if recording was started, -1 on error
 * \version LibVLC 2.1.0 or later
 */
LIBVLC_API int libvlc_media_player_record_start( libvlc_media_player_t *p_mi, const char *psz_filename );

/**
 * Stop recording the current media.
 *
 * This method requests that the recording stop, and will return immediately. Recording
 * will not stop immediately.
 *
 * When the recording actually stops some short time later and the new file has
 * finished being written, a libvlc_MediaPlayerRecordingFinished event will be sent via
 * the media player event manager. The newly recorded file will not be visible or
 * available until after this event has been sent.
 *
 * The event data will contain the full name of the file that was created. The filename
 * will either be that as was specified by the calling application on invoking
 * libvlc_media_player_record_start(), or the filename that vlc automatically generated
 * if the calling application did not supply its own filename. In either case the
 * filename will contain the automatically appended file extension.
 *
 * There is no need to invoke this method to stop the recording if the media is stopped
 * or finishes playing normally.
 *
 * \param p_mi media player
 * \return 0 if recording was stopped, -1 on error
 * \version LibVLC 2.1.0 or later
 */
LIBVLC_API int libvlc_media_player_record_stop( libvlc_media_player_t *p_mi );

/**
/** @} media_player */

vi lib/event.c
static const event_name_t event_list[] = {
    DEF(MediaPlayerAudioDevice)
    DEF(MediaPlayerRecordableChanged)
    DEF(MediaPlayerRecordingFinished)

vi lib/libvlc.sym
libvlc_media_player_has_vout
libvlc_media_player_is_recordable
libvlc_media_player_is_recording
libvlc_media_player_is_seekable

libvlc_media_player_previous_chapter
libvlc_media_player_record_start
libvlc_media_player_record_stop
libvlc_media_player_release

vi lib/media_player.c
static int
input_recordable_changed( vlc_object_t *p_this, char const *psz_cmd,
                          vlc_value_t oldval, vlc_value_t newval,
                          void *p_userdata );
static int
file_recording_finished( vlc_object_t *p_this, char const *psz_cmd,
                         vlc_value_t oldval, vlc_value_t newval, void *p_data );
static void release_input_thread( libvlc_media_player_t *p_mi )
{
    var_DelCallback( p_input_thread, "can-record",
                     input_recordable_changed, p_mi );
}
static int
input_recordable_changed( vlc_object_t *p_this, char const *psz_cmd,
                          vlc_value_t oldval, vlc_value_t newval,
                          void *p_userdata )
{
    VLC_UNUSED(p_this);
    VLC_UNUSED(psz_cmd);
    VLC_UNUSED(oldval);

    libvlc_media_player_t *p_mi = p_userdata;
    libvlc_event_t event;

    event.type = libvlc_MediaPlayerRecordableChanged;
    event.u.media_player_recordable_changed.new_recordable = newval.b_bool;

    libvlc_event_send( p_mi->p_event_manager, &event );
    return VLC_SUCCESS;
}
static int file_recording_finished(vlc_object_t *p_this, char const *psz_cmd,
                                   vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
    VLC_UNUSED(p_this);
    VLC_UNUSED(psz_cmd);
    VLC_UNUSED(oldval);

    libvlc_media_player_t *p_mi = p_data;
    libvlc_event_t event;

    event.type = libvlc_MediaPlayerRecordingFinished;
    event.u.media_player_recording_finished.psz_filename = newval.psz_string;

    libvlc_event_send(p_mi->p_event_manager, &event);
    return VLC_SUCCESS;
}
libvlc_media_player_new( libvlc_instance_t *instance )
{
    var_Create (mp, "recording-finished", VLC_VAR_STRING);
    var_AddCallback (mp, "recording-finished", file_recording_finished, mp);
}
static void libvlc_media_player_destroy( libvlc_media_player_t *p_mi )
{
    var_DelCallback( p_mi, "recording-finished", file_recording_finished, p_mi );
}
int libvlc_media_player_play( libvlc_media_player_t *p_mi )
{
    var_AddCallback( p_input_thread, "can-record", input_recordable_changed, p_mi );
    if( input_Start( p_input_thread ) )
    {
        var_DelCallback( p_input_thread, "can-record", input_recordable_changed, p_mi );
    }
}
bool libvlc_media_player_is_recordable( libvlc_media_player_t *p_mi )
{
    input_thread_t *p_input_thread;
    bool b_can_record;

    p_input_thread = libvlc_get_input_thread( p_mi );
    if( !p_input_thread )
        return false;

    b_can_record = var_GetBool( p_input_thread, "can-record" );

    vlc_object_release( p_input_thread );
    return b_can_record;
}
bool libvlc_media_player_is_recording( libvlc_media_player_t *p_mi )
{
    input_thread_t *p_input_thread;
    bool b_record;

    p_input_thread = libvlc_get_input_thread( p_mi );
    if( !p_input_thread )
        return false;

    b_record = var_GetBool( p_input_thread, "record" );

    vlc_object_release( p_input_thread );
    return b_record;
}
int libvlc_media_player_record_start( libvlc_media_player_t *p_mi, const char* psz_filename )
{
    input_thread_t *p_input_thread;

    p_input_thread = libvlc_get_input_thread( p_mi );
    if( !p_input_thread )
        return -1;

    var_SetString( p_input_thread, "input-record-path", psz_filename );
    var_SetBool( p_input_thread, "record", true );

    vlc_object_release( p_input_thread );
    return 0;
}
int libvlc_media_player_record_stop( libvlc_media_player_t *p_mi )
{
    input_thread_t *p_input_thread;

    p_input_thread = libvlc_get_input_thread( p_mi );
    if( !p_input_thread )
        return -1;

    var_SetBool( p_input_thread, "record", false );

    vlc_object_release( p_input_thread );
    return 0;
}

vi modules/stream_out/record.c
struct sout_stream_sys_t
{
    char *psz_record_file;
};
static int Open( vlc_object_t *p_this )
{
    p_sys->psz_record_file = NULL;

    return VLC_SUCCESS;
}
static void Close( vlc_object_t * p_this )
{
    if( p_sys->psz_record_file ) {
        for( vlc_object_t *p_mp = p_stream->p_parent; p_mp; p_mp = p_mp->p_parent )
        {
            if( var_Type( p_mp, "recording-finished" ) )
            {
                var_SetString( p_mp, "recording-finished", p_sys->psz_record_file );
                break;
            }
        }

        free( p_sys->psz_record_file );
    }
    TAB_CLEAN( p_sys->i_id, p_sys->id );
    free( p_sys->psz_prefix );
    free( p_sys );
}
static int OutputNew( sout_stream_t *p_stream,
                      const char *psz_muxer, const char *psz_prefix, const char *psz_extension  )
{
    if( psz_file && psz_extension )
    {
        p_sys->psz_record_file = strdup( psz_file );
        var_SetString( p_stream->p_libvlc, "record-file", psz_file );
    }
}

vi src/input/var.c 
void input_ControlVarInit ( input_thread_t *p_input )
{
    /* ES Out */
    var_Create( p_input, "input-record-path", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
}

2016年4月11日 星期一

VLC 延遲 錄影 抓圖

延遲主串流到新串流
vlc.exe --sout=#rtp{sdp=rtsp://:8554/ch1} --sout-all --sout-keep rtsp://169.254.1.168:554/live2.sdp --network-caching=5000 --intf telnet --telnet-host="0.0.0.0" --telnet-port="4212" --telnet-password="1234" --verbose=2 --file-logging --logfile=vlc-log-4212.txt

抓圖
vlc.exe rtsp://localhost:8554/ch1 --vout dummy --intf telnet --telnet-host="0.0.0.0" --telnet-port="4213" --telnet-password="1234" --verbose=2 --file-logging --logfile=vlc-log-4213.txt
telnet localhost 4213
snapshot
shutdown

錄影
vlc.exe --intf telnet --telnet-host="0.0.0.0" --telnet-port="4214" --telnet-password="1234" --verbose=2 --file-logging --logfile=vlc-log-4214.txt
telnet localhost 4214
new ch1 broadcast enabled
setup ch1 input rtsp://localhost:8554/ch1
setup ch1 output #file{mux=ps,dst='R:\temp\aaa.ps'}
control ch1 play
control ch1 stop
shutdown

延遲主串流,可直接抓圖
vlc.exe rtsp://169.254.1.168:554/live2.sdp --network-caching=5000 --vout dummy --intf telnet --telnet-host="0.0.0.0" --telnet-port="4212" --telnet-password="1234" --verbose=2 --file-logging --logfile=vlc-log.txt
telnet localhost 4212
snapshot
shutdown

2016年4月8日 星期五

在 VLC 的 log 上增加時間

vi modules/logger/file.c

static void LogText(void *opaque, int type, const vlc_log_t *meta,
                    const char *format, va_list ap)

    struct timespec ts;
    struct tm curtime;
    char tmBuf[128];
    timespec_get(&ts, TIME_UTC);
    if (localtime_r(&ts.tv_sec, &curtime) == NULL) {
        gmtime_r(&ts.tv_sec, &curtime);
    }
    if (strftime(tmBuf, sizeof(tmBuf), "%M%S", &curtime) == 0) {
        strcpy(tmBuf, "no time");
    }
    fprintf(stream, "%s.%03d %s%s: ", tmBuf, (int)(ts.tv_nsec / 1000000), meta->psz_module, msg_type[type]);

2016年4月6日 星期三

三 VideoLAN Compile 成熟篇

不了解 git 即使能編譯完成,那也只是幸運!
apt-get install apt-show-version

git clone 會取得完整的程式庫,clone 後你就可以使用下列命令

$ git tag -n
列出主要版本 tag
$ git log --pretty=format:'%H %h %ci' -n 10
列出最近的10個版本
$ git log --tags --simplify-by-decoration --pretty="format:%ci %h %d %H"

$ git checkout tags/<tag_name>
使的整的程式庫呈現,tag版本的狀態
$ git checkout master
$ git checkout 035d652
$ git checkout 'master@{2016-03-24 10:02:16}'
$ git checkout -- HEAD

$ git reset --hard
$ git clean -f -d
完整地回復到最後版本,並且刪除不必要的檔案

針對某一檔案
$ git log filename
$ git --no-pager diff 99388eb:filename bdc798c:filename
$ git archive -o foo.tar 99388eb


話說回到 VideoLAN,因為整個計畫太大,所以將一些子計畫放入 contrib 目錄
而 make prebuilt 就是使用編譯好的子計畫
主計畫和子計畫並不一定能同步,據我的經驗要同步很難,所以編譯不過是正常的
真正的重點就是調整計畫的版本

以下是遇到的問題紀錄
add-apt-repository ppa:ubuntu-wine/ppa
add-apt-repository ppa:n-muench/vlc
apt-get update
apt-get install wine1.8-dev
apt-get install libsidplay2-dev
apt-get install libdvd-pkg
apt-get install libdvdread-dev

LUA 要使用32位元的版本,否則會遇下列問題
checking for LUA... no
configure: WARNING: No package 'lua5.2' found, trying lua 5.1 instead
checking for LUA... no
configure: WARNING: No package 'lua5.1' found, trying lua >= 5.1 instead
checking for LUA... yes
checking for luac... luac
configure: error: You need 32-bits luac when using lua from contrib.

32位元版本的安裝就是加上 :i386
apt-get install lua5.2:i386
apt-get install lua5.2-dev:i386
apt-get install liblua5.2:i386
apt-get install liblua5.2-dev:i386

在 contrib 下的 lua 要回復到 5.1 版,之後的版本會遇到 module 的問題
如 lua/modules/common.lua 之
module("common",package.seeall)

下列命令可以測試 lua 版本
contrib/i686-w64-mingw32/bin# ./luac -v
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio

主程式版本
d445720262fbc0eb6c7e3e1244280aee2c492e08 d445720 2016-03-23 14:47:50 +0100

LUA 回復版本
git checkout c433918ec056f0b6a1b6ecbce01e03f62bc68ebf contrib/src/lua

ffmpeg 回復版本
vi contrib/src/ffmpeg/rules.mak
ifdef USE_FFMPEG
else
#HASH=HEAD
HASH=73b0324


2016年3月16日 星期三

VLC 追蹤之五 VoutCreate

載入 avcodec, 並登記 pf_vout_format_update
src/input/decoder.c
input_DecoderNew -> decoder_New -> CreateDecoder
    p_dec->pf_vout_format_update = vout_update_format;
input_DecoderNew -> decoder_New -> CreateDecoder -> LoadDecoder
p_dec->p_module = module_need( p_dec, "decoder", "$codec", false );

載入 avcodec 時, 登記 pf_decode_video, get_format
modules/codec/avcodec/avcodec.c
    set_capability( "decoder", 70 )
    set_callbacks( OpenDecoder, CloseDecoder )

OpenDecoder
    ret = InitVideoDec( p_dec, avctx, p_codec );

modules/codec/avcodec/video.c
InitVideoDec
    p_context->get_format = ffmpeg_GetFormat;
    p_dec->pf_decode_video = DecodeVideo;

透過 pf_decode_video 執行 DecodeVideo
src/input/es_out.c
input_DecoderNew -> decoder_New -> vlc_clone(DecoderThread)
src/input/decoder.c
DecoderThread -> DecoderProcess -> DecoderProcessVideo -> DecoderDecodeVideo
    while( (p_pic = p_dec->pf_decode_video( p_dec, pp_block ) ) )

modules/codec/avcodec/video.c
DecodeVideo -> lavc_UpdateVideoFormat
    return decoder_UpdateVideoFormat(dec);

透過 pf_vout_format_update 執行 vout_update_format
include/vlc_codec.h
decoder_UpdateVideoFormat
    return dec->pf_vout_format_update( dec );

src/input/decoder.c
vout_update_format
    p_vout = input_resource_RequestVout( p_owner->p_resource,

src/input/resource.c
input_resource_RequestVout -> RequestVout
    p_vout = vout_Request( p_resource->p_parent, &cfg );

src/video_output/video_output.c
vout_Request -> VoutCreate
    vout->p->spu = spu_Create(vout);

src/video_output/vout_subpictures.c
spu_Create -> sys->text = SpuRenderCreateAndLoadText
    text->p_module = module_need(text, "text renderer", "$text-renderer", false);
在此載入 freetype
spu_Create -> sys->scale = SpuRenderCreateAndLoadScale
    scale->p_module = module_need(scale, "video filter2", NULL, false);
在此載入 swscale
spu_Create -> sys->scale_yuvp = SpuRenderCreateAndLoadScale
    scale->p_module = module_need(scale, "video filter2", NULL, false);
在此載入 yuvp

src/video_output/video_output.c
vout_Request -> VoutCreate -> vout_display_window_New
src/video_output/window.c
vout_display_window_New -> vout_window_New -> vlc_module_load(vout window)
在此載入 qt4

src/video_output/video_output.c
vout_Request -> VoutCreate -> vlc_clone(Thread)
Thread -> ThreadControl -> ThreadStart -> vout_OpenWrapper
src/video_output/vout_wrapper.c
vout_OpenWrapper -> vout_NewDisplay
src/video_output/display.c
vout_NewDisplay -> DisplayNew -> vout_display_New -> module_need(vout display)
在此載入 direct3d9

src/video_output/video_output.c
vout_Request -> VoutCreate -> spu_Attach
src/video_output/vout_subpictures.c
spu_Attach -> spu->p->text = SpuRenderCreateAndLoadText
    text->p_module = module_need(text, "text renderer", "$text-renderer", false);
在此載入 freetype


2016年3月15日 星期二

VLC 追蹤之四 aout stream

src/playlist/engine.c -> playlist_Create -> input_resource_GetAout
src/input/resource.c -> input_resource_GetAout -> aout_New
src/audio_output_output.c -> aout_New -> module_need(audio output)

載入 mmdevice 時登記了 start
modules/audio_output/mmdevice.c
    set_capability("audio output", 150)
    set_callbacks(Open, Close)
static int Open(vlc_object_t *obj)
    aout->start = Start;

src/input/decoder.c
input_DecoderNew -> decoder_New -> CreateDecoder
    p_dec->pf_aout_format_update = aout_update_format;
    p_dec->pf_vout_format_update = vout_update_format;
input_DecoderNew -> decoder_New -> CreateDecoder -> LoadDecoder
    p_dec->p_module = module_need( p_dec, "decoder", "$codec", false );
此時載入了 avcodec, 並登記了 pf_vout_format_update

載入 g711 時登記了 pf_decode_audio
modules/codec/g711.c
    set_capability( "decoder", 100 )
    set_callbacks( DecoderOpen, DecoderClose )
static int DecoderOpen( vlc_object_t *p_this )
    p_dec->pf_decode_audio = DecodeBlock;

透過 pf_decode_audio 執行 DecodeBlock
src/input/es_out.c
input_DecoderNew -> decoder_New -> vlc_clone(DecoderThread)
src/input/decoder.c
DecoderThread -> DecoderProcess -> DecoderProcessAudio -> DecoderDecodeAudio
static void DecoderDecodeAudio( decoder_t *p_dec, block_t *p_block )
    while( (p_aout_buf = p_dec->pf_decode_audio( p_dec, pp_block ) ) )

透過 pf_aout_format_update 執行 aout_update_format
modules/codec/g711.c -> DecodeBlock -> decoder_NewAudioBuffer
include/vlc_codec.h -> decoder_UpdateAudioFormat
    return dec->pf_aout_format_update( dec );

透過 start 執行 Start
src/input/decoder.c -> aout_update_format -> aout_DecNew
src/audio_output/dec.c -> aout_DecNew -> aout_OutputNew
src/audio_output/output.c -> aout_OutputNew
    aout->start (aout, fmt)

最後載入 aout stream
modules/audio_output/mmdevice.c -> Start ->vlc_module_load(aout stream)

順便一提,之後載入 audio volume
src/audio_output/dec.c -> aout_DecNew -> aout_volume_SetFormat
src/audio_output/volume.c -> aout_volume_SetFormat -> module_need(audio volume)

載入 audio filter
src/audio_output/dec.c -> aout_DecNew -> aout_FiltersNew
src/audio_output/filters.c -> aout_FiltersNew -> AppendFilter(audio filter) -> CreateFilter(audio filter) -> module_need(audio filter)


載入 audio converter(audio_format)
src/audio_output/filters.c -> AppendFilter -> aout_FiltersPipelineCreate (Format) -> TryFormat -> FindConvert -> CreateFilter(audio converter) -> module_need(audio converter) -> audio_format

載入 audio converter(trivial)
src/audio_output/filters.c -> aout_FiltersNew -> aout_FiltersPipelineCreate (Remix channels) -> TryFormat -> FindConvert -> CreateFilter(audio converter) -> module_need(audio converter) -> trivial

載入 audio resampler(samplerate)
src/audio_output/filters.c -> aout_FiltersNew -> FindResampler -> CreateFilter(audio resampler) -> module_need(audio resampler) -> samplerate





2016年3月14日 星期一

VLC 追蹤之三 packetizer

設定 p_es_out_display->pf_control = EsOutControl
../src/input/input.c -> Create -> input_EsOutNew
    p_input->p->p_es_out_display = input_EsOutNew( p_input, p_input->p->i_rate );

../src/input/es_out.c
es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate )
    out->pf_control = EsOutControl;

設定 
    p_es_out->pf_control = Control
    p_es_out->p_out = p_es_out_display
../src/input/input.c -> Init
    p_input->p->p_es_out = input_EsOutTimeshiftNew( p_input, p_input->p->p_es_out_display, p_input->p->i_rate );

../src/input/es_out_timeshift.c
es_out_t *input_EsOutTimeshiftNew( input_thread_t *p_input, es_out_t *p_next_out, int i_rate )
    p_out->pf_control = Control;
    p_out->p_sys      = p_sys;
    p_sys->p_out = p_next_out;

觸發 pf_control, 此時是 es_out_timeshift.c 內的 Control
../src/input/input.c
Init -> InitPrograms -> es_out_SetMode
    es_out_SetMode( p_input->p->p_es_out, i_es_out_mode );
../src/input/es_out.h
static inline void es_out_SetMode( es_out_t *p_out, int i_mode )
{
    int i_ret = es_out_Control( p_out, ES_OUT_SET_MODE, i_mode );
    assert( !i_ret );
}
../include/vlc_es_out.h
es_out_Control -> es_out_vaControl( out, i_query, args );
static inline int es_out_vaControl( es_out_t *out, int i_query, va_list args )
{
    return out->pf_control( out, i_query, args );
}


一樣透過 es_out_Control 觸發 pf_control, 此時是 es_out.c 內的 EsOutControl
../src/input/es_out_timeshift.c
static int Control( es_out_t *p_out, int i_query, va_list args )
    i_ret = ControlLocked( p_out, i_query, args );
static int ControlLocked( es_out_t *p_out, int i_query, va_list args )
    es_out_sys_t *p_sys = p_out->p_sys;
    int rt =  CmdExecuteControl( p_sys->p_out, &cmd );
static int CmdExecuteControl( es_out_t *p_out, ts_cmd_t *p_cmd )
    int rt = es_out_Control( p_out, i_query, p_cmd->u.control.u.i_int );

../src/input/es_out.c
EsOutControl -> EsOutControlLocked -> EsSelect -> EsCreateDecoder -> input_DecoderNew
../src/input/decoder.c
input_DecoderNew -> decoder_New -> CreateDecoder -> LoadDecoder
    p_dec->p_module = module_need( p_dec, "packetizer", "$packetizer", false );

最後帶入 packetizer