見出し画像

paranoiaライブラリを使った開発:音楽CDのリッピング

LinuxでCDのリッピングができるようになったので、そのような機能を自分のソフト開発でも使えるようになりたくて、cdparanoiaAsunderなどで使っているライブラリの使い方を調べました。

ここでは、音楽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バイトとなります。

CDのデータ構造
SOUND HOUSEのコラム「蠱惑の楽器たち 32.音楽と電気の歴史8」より抜粋

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情報を取得するときに使えます。


この記事が気に入ったらサポートをしてみませんか?