paranoiaライブラリを使った開発:音楽CDのリッピング
LinuxでCDのリッピングができるようになったので、そのような機能を自分のソフト開発でも使えるようになりたくて、cdparanoiaやAsunderなどで使っているライブラリの使い方を調べました。
ここでは、音楽CDから曲データを読み出す方法について、paranoiaライブラリを使った方法について説明します。
ネット上の音楽CDのデータベースCDDB(Gnudb)から音楽CDの楽曲情報を取得する方法に関しては、別の記事で書いています。
CDのデータ構造は、Lead In、音楽データ、Lead Outからなります。
Lead Inには音楽データの各トラック(曲)の先頭位置(セクター単位)などを保存したTOC(Table of Contents)のデータが保存されています。
音楽データは、1/75秒を1セクターとした構造に、2チャンネルのPCMデータが保存されています。バイト数でいうと1セクターは2,352バイトとなります。
paranoiaライブラリは、libcdda_interfaceと、libcdda_paranoiaの2つからなり、libcdda_interfaceはディスクからの読み込みなどの低レベル、libcdda_paranoiaはlibcdda_interfaceを使ってセクター単位の読み込みなどを行う高レベルのAPIを提供しているようです。
libcdda_interfaceが提供する関数は関数名の先頭にcdda_が、libcdda_paranoiaが提供する関数の関数名の先頭にはparanoia_が付いています。
下記は、音楽CDのTOCを読み込んで表示した後、指定した1曲分をtest.wavとしてファイルに保存するC/C++のサンプルプログラムです。
できるだけソフト全体の流れがわかりやすいように、エラー処理などは極力省いてしまっています。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
// (1)
extern "C" {
#include <cdda_interface.h>
#include <cdda_paranoia.h>
}
static char wavHead[44] = {
'R', 'I', 'F', 'F', 0x24, 0x00, 0x00, 0x00,
'W', 'A', 'V', 'E', 'f', 'm', 't', ' ',
0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00,
0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00,
0x04, 0x00, 0x10, 0x00, 'd', 'a', 't', 'a',
0x00, 0x00, 0x00, 0x00};
static void write_wav_header(int f, long bytes)
{
*((int *)&wavHead[4]) = bytes + 44 - 8;
*((int *)&wavHead[40]) = bytes;
write(f, wavHead, sizeof(wavHead));
}
static void callback(long inpos, int function)
{
}
/*
display_toc関数はcdparanaiaのソースコードから借用
*/
static void display_toc(cdrom_drive *d)
{
long audiolen=0;
printf("\nTable of contents (audio tracks only):\n");
printf("track length begin copy pre ch\n");
printf("===========================================================\n");
for(int i = 1; i <= d->tracks; ++i){
if(cdda_track_audiop(d,i) > 0){
long sec = cdda_track_firstsector(d,i);
long off = cdda_track_lastsector(d,i) - sec+1;
printf("%3d. %7ld [%02d:%02d.%02d] %7ld [%02d:%02d.%02d] %s %s %s\n",
i,
off, (int)(off/(60*75)), (int)((off/75)%60), (int)(off%75),
sec, (int)(sec/(60*75)), (int)((sec/75)%60), (int)(sec%75),
cdda_track_copyp(d,i) ? " OK" : " no",
cdda_track_preemp(d,i) ? " yes" : " no",
cdda_track_channels(d,i)==2 ? " 2" : " 4");
audiolen += off;
}
}
printf("TOTAL %7ld(%ldsec) [%02d:%02d.%02d] (audio only)\n",
audiolen, (audiolen + 75 - 1)/75,
(int)(audiolen/(60*75)), (int)((audiolen/75)%60),
(int)(audiolen%75));
}
int main(int argc, char *argv[])
{
// (2)
cdrom_drive *d = cdda_find_a_cdrom(CDDA_MESSAGE_PRINTIT, NULL);
if(d == NULL){
printf("/dev/cdrom isn't accsessible.\n");
return -1;
}
// (3)
int ret = cdda_open(d);
if(ret < 0){
printf("can't open cd.\n");
cdda_close(d);
return -1;
}
// (4)
cdda_verbose_set(d, CDDA_MESSAGE_PRINTIT, CDDA_MESSAGE_PRINTIT);
cdda_speed_set(d, -1);
display_toc(d);
// (5)
cdrom_paranoia *p = paranoia_init(d);
// (6)
int mode = PARANOIA_MODE_FULL ^ PARANOIA_MODE_NEVERSKIP;
paranoia_modeset(p, mode);
int target_track = 4; // 保存するトラック番号
if(target_track <= 0) target_track = 1;
if(target_track > d->tracks) target_track = d->tracks;
// (7)
int target_first_sector = cdda_track_firstsector(d, target_track);
int target_last_sector = cdda_track_lastsector(d, target_track);
int target_length = target_last_sector - target_first_sector + 1;
printf("save target: track=%d: sector=%d-%d, length=%d\n",
target_track, target_first_sector, target_last_sector, target_length);
int out = open("test.wav", O_RDWR|O_CREAT|O_TRUNC, 0666);
write_wav_header(out, target_length * CD_FRAMESIZE_RAW);
// (8)
paranoia_seek(p, target_first_sector, SEEK_SET);
for(int i = 0; i < target_length; ++i){
// (9)
int16_t *readbuf = paranoia_read(p, callback);
if(readbuf) write(out, (char *)readbuf, CD_FRAMESIZE_RAW);
printf("\r%d/%d", i, target_length); fflush(stdout);
}
printf("\nfinished.\n");
// (10)
cdda_close(d);
close(out);
return 0;
}
main関数の処理を順に見ていけば説明がなくても分かると思いますが、簡単に説明を加えておきます。
(1) paranoiaライブラリを使うため、cdda_interface.hとcdda_paranoia.hをインクルードします。c++でコンパイルするためextern "C"でくくっています。
(2) cdda_find_a_cdromでデバイスを見つけます。
(3) cdda_openでディスクをオープンします。オープンした時点でTOCが読み込まれるようなので、 オープン後にdisplay_tocを呼べば、読み込んだTOCを確認することができます。
(4) 参考のためここではcdda_verbose_setやcdda_speed_setを呼び出していますが、これらの呼び出しは無くても動作します。
ここまでが、cdda_interfaceで必要な操作です。cdda_find_a_cdromとcdda_openの2つを呼び出すだけです。
次からは高レベルインタフェースのcdda_paranoiaを使って音楽データを読み出す操作です。
(5) paranoia_initで初期化します。
(6) paranoia_modesetで読み出しモードを設定します。これは無くても動作します。
(7) cdda_track_firstsectorとcdda_track_lastsectorを使って、読み出したいトラック(曲)の先頭と末尾のセクター位置を、読み出したTOCの情報から取得します。
(8) paranoia_seekで読み出し開始セクター位置に移動します。
(9) paranoia_readで音楽データを読み出します。読み出し単位は1セクター単位(2,352バイト)です。この値はCD_FRAMESIZE_RAWとして定義されています。1曲分の音楽データを読み込むには、paranoia_readで1曲分のセクター数だけ繰り返しリードすれば良いです。
(10) 使い終わったらcdda_openでオープンした物をcdda_closeでクローズします。
以上、音楽データの読み出しには、paranoia_initで初期化、 paranoia_seekで先頭に移動、paranoia_readで読み出す、の3つの操作でOKです。
上記のサンプルプログラムをcdda_simple.cppなどのファイル名で保存して、下記のようにコンパイルすれば良いでしょう。
$ g++ -o cdda_simple cdda_simple.cpp -lcdda_interface -lcdda_paranoia
音楽CDをセットして、コンパイルしたソフトを実行すると、TOCを表示した後、target_trackにセットしたトラック番号の曲がtest.wavファイルに保存されます。
下記は、「角川映画スペシャル」のCDを入れて実行した場合の例です。全部で12曲のCDであることがわかると思います。サンプルでは4曲目(target_track=4)がtest.wav名のファイルに保存されます。
$ ./cdda_simple
Checking /dev/cdrom for cdrom...
Testing /dev/cdrom for SCSI/MMC interface
SG_IO device: /dev/sr0
CDROM model sensed sensed: HL-DT-ST DVDRAM GP77N LC02
Checking for SCSI emulation...
Drive is ATAPI (using SG_IO host adaptor emulation)
Checking for MMC style command set...
Drive is MMC style
DMA scatter/gather table entries: 1
table entry size: 122880 bytes
maximum theoretical transfer: 52 sectors
Setting default read size to 27 sectors (63504 bytes).
Verifying CDDA command set...
Expected command set reads OK.
Table of contents (audio tracks only):
track length begin copy pre ch
===========================================================
1. 19495 [04:19.70] 0 [00:00.00] no no 2
2. 16412 [03:38.62] 19495 [04:19.70] no no 2
3. 18280 [04:03.55] 35907 [07:58.57] no no 2
4. 20760 [04:36.60] 54187 [12:02.37] no no 2
5. 21293 [04:43.68] 74947 [16:39.22] no no 2
6. 19192 [04:15.67] 96240 [21:23.15] no no 2
7. 17838 [03:57.63] 115432 [25:39.07] no no 2
8. 17762 [03:56.62] 133270 [29:36.70] no no 2
9. 14875 [03:18.25] 151032 [33:33.57] no no 2
10. 15610 [03:28.10] 165907 [36:52.07] no no 2
11. 16625 [03:41.50] 181517 [40:20.17] no no 2
12. 17460 [03:52.60] 198142 [44:01.67] no no 2
TOTAL 215602(2875sec) [47:54.52] (audio only)
save target: track=4: sector=54187-74946, length=20760
トータルの再生時間(sec)と各トラックの開始位置(begin)は、libcddbでCDDB(Gnudb)からCD情報を取得するときに使えます。
この記事が気に入ったらサポートをしてみませんか?