0

繼續解決 Windows 的問題

| 11/16/2010
- 刪除沒有辦法刪除的檔案
有的時候你會發現某些檔案沒有辦法刪除,你會得到存取被拒的結果。這些檔案有可能是你電腦在重灌之前留下來的檔案,因為使用者資訊改變的關係,那些本來被設定成特殊權限的系統檔案一時之間找不到主人,自然你就缺少對該檔案控制的權力。使用下面的指令可以幫你重新取回檔案的所有權:

takeown /F filename /R

這個指令會將檔案的所有者轉移到現在登入的使用者身上,然後再改變該檔案的存取權限,讓管理者擁有最高控制權:

icacls filename /grant Administrators:F

接下來,你就可以快樂的砍掉他了!
這一連串的指令當然可以寫成 batch 檔案,之後好存取:

@echo off
takeown /F %1 /R
icacls %1 /grant Administrators:F
del %1

然後將檔案存成 forcedel.bat 就大功告成啦!只要把檔案拖到這個 batch file,檔案自然會被摧毀!

- 開啟檔案位置
這是一個從 Vista 開始很好用的功能。你可以經由這個右鍵指令直接找到捷徑本來的檔案位置。不知道從何時開始,你會發現當你按下去之後,這個指令一點反應都沒有!解決的辦法:

找出 Internet Explorer 9 的更新,然後反安裝他吧…老師說過,Beta 版的東西少碰為妙…
0

wchar_t 的長度…

| 7/02/2010
wchar_t 是 ANCI C 提供支援 Unicode 的方案。使用方式跟一般 char * 字串很像,整個介面都是差不多的。

char * anciStr = "測試"
wchar_t * uniStr = L"測試" // 注意這裡需要加上一個 micro

而我們常用的字串函式 strlen, strcpy 等,自然就會有對應的東西出現:wcslen, wcscpy。這些東西則是定義再 wchar.h 裡面。

雖然支援了 Unicode 是很好,但是並不是所有的作業系統對這些東西的編碼定義都相同。今天如果要做跨平台的系統程式設計,就得要不斷的面對每個系統對字元符號的編碼定義不同的問題。好比如說,在 Windows 上面, char 字元預設是 ASCII +1 的 8bit 字,wchar_t 則是 UTF-16 Little Endian。這看起來像是很合情合理,但是像是 Linux、Mac OS X 這種全面支援 Unicode 的系統而言,char 早就已經是 Unicode 了。同樣是 Unicode ,卻依然有大大的不同!在這兩的系統上面,char 是被定義成 UTF-8(今天 Windows 在 UTF-8 上面是有 BOM 的,而這裡沒有。其實 BOM 對 UTF-8 而言很沒意義),而 wchar_t 則是 UTF-32(Big Endian 跟 Little Endian 都有可能,看 CPU)。所以說,我們用函式測試的時候就會出現這樣的結果:

strlen(anciStr) // Windows 會出現 4,Linux、Mac 則是 6
wcslen(uniStr) // 這裡統一會出現 2
看的懂差異的來源嗎?

今天 Cocoa Foundation 提供 NSString 的 Class,這個 Class 是以 UTF-16 Big Endian 儲存的。但是該 Class 本身具備好了許多編碼轉換的函式,可以讓你在 不同的編碼之間轉換!所以說,NSString 跟 wchar_t 之間的變化,應該是:

wchar_t * string = malloc([nsString length] * sizeof(wchar_t) + 1);
wcscpy(string, (const wchar_t *)[nsString cStringUsingEncoding:NSUTF32LittleEndianStringEncoding]);
0

Play Music using AudioUnit

| 6/29/2010
AudioUnit playback - Introtuction of CoreAudio Programming
AudioUnit 是 Mac OS X 在 Audio Programming 上面使用,代表一段音樂資料的基本單位。一般來說,如果只是要讓應用程式具有播放音樂的能力,並不需要使用到這種東西。連同蘋果 10.2 開始提供的 CoreAudio API 在內,都屬於低階程式編輯的東西。一般來說,如果只是要讓你的程式播放音樂,還有 QTKit 之類高階的 API 讓你簡單的達到播放音樂的功能。但是如果你希望做到一些更加進階的功能:編輯、Codec 的開發,你就必須要瞭解這個東西。這邊只有簡單記錄一下要如何使用 AudioUnit 來播放音樂。

我們會使用到一些 Framework,必須要把他們連結進來:
  1. AudioUnit.framework
  2. AudioToolbox.framework
  3. CoreService.framework

首先,我們使用 Component 來取得使用者設定的 Default Output Device Unit。AudioUnit 分成許多不同的種類,他們可以是 Outputs, Mixers, or DSP。閱讀 AUComponent.h 可以得到其他資訊。我們可以簡單的使用 ComponentDescription 來取得 Output Audio Unit。

Code:
ComponentDescription desc;
Component comp;
AudioUnit outputUnit;
OSStatus err;

// 設定 Component 型態
desc.componentType = kAudioUnitType_Output;
// 每一種型態都有一個 sub type,用來深入描述該型態
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
// 下面的部份,所有的 AudioUint 都是一樣的,依樣畫葫蘆就可以了
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;

// 取得與描述相同的 Component
comp = FindNextComponent(NULL, &desc);
if (comp == NULL) exit(-1);
// 取得 Output AudioUnit
err = OpenAComponent(comp, &outputUnit);

接下來,我們要設定 AudioUnit 的參數。
AudioUnit 有許多不同的參數可以設定,其中最常使用到的兩個函數就屬於 AudioUnitGetProperty 以及 AudioUnitSetProperty 兩個。其中,我們也經常使用 AudioUnitGetPropertyInfo 來取得一些資訊用以避免設定上的錯誤。

在 AudioUnit 的眾多參數裡面,最重要的一項就是 AudioStreamBasicDescription (ASBD)。這項變數裡面存放了關於 Audio Stream 的描述,包含 simple rate、Package information 以及 Stream 格式。我們可以思考 AudioUnit 包含了兩個最大的部份:input 跟 output。所以說我們可以為這兩個部分設定 ASBD。(注意:AudioUnit 型態本身就是個 Reference)

Code:
UInt32 size;
Boolean isWritable;
AudioStreamBasicDescription outputDesc;
AudioStreamBasicDescription inputDesc;

// 取得 Stream format 的 size,以及是否可以寫入。
err = AudioUnitGetPropertyInfo(outputUnit,
                               kAudioUnitPorperity_StreamFormat,
                               kAudioUnitScope_Output,
                               0, &size, &isWritable);

// 取得 Stream Format
err = AudioUnitGetProperty(outputUnit,
                           kAudioUnitProperty_StreamFormat,
                           kAudioUnitScope_Output,
                           0, &outputDesc, &size);

// 將 input 跟 output 的 stream format 設定相同
err = AudioUnitSetProperty(outputUnit,
                           kAudioUnitProperty_StreamFormat,
                           kAudioUnitScope_Input,
                           0, &outputDesc, &size);

// 注意,在所有 Property 設定結束之後才使用 AudioUnitInitialize。這個函式的消費很大。
err = AudioUintInitialize(outputUint);
這裡需要注意的地方還有一個,所有的 err 都可以作為結束的判斷式。

我們得到的 ASBD 資料格式應該如下面的範例:
接下來我們要設定一個 Render Callback 來做為 AudioUnit 取得播放資料的緩衝區的地方。我們使用 kAudioUnitProperty_SetRenderCallback 以及 AURenderCallbackStruct。被設定的函式只有在 Audio Converter 需要資料的時候才會被呼叫。在這裡我們假設被呼叫的函式名稱為 MyFileRenderProc。AURenderCallbackStruct 有兩個 Member:inputProc 以及 inputProcRefCon。前者為我們要設定的 callback function 名稱,後者則是該函式的參數。

Code:
AURenderCallbackStruct renderCallback;
memset(&renderCallback, 0, sizeof(AURenderCallbackStruct));

// 設定 Render Callback。因為播放的時候不需要傳入參數,所以 RefCon 設定為 0
renderCallback.inputProc = MyFileRenderProc;
renderCallback.inputProcRefCon = 0;

// 設定到 AudioUnit 裡面
err = AudioUnitSetProperty(outputUnit,
                           kAudioUnitProperty_SetRenderCallback,
                           kAudioUnitScope_Input,
                           0, &renderCallback,
                           sizeof(AURenderCallbackStruct));


這些設定都設定完成之後,我們就來讀取音樂檔案,並且放置到緩衝區裡面。當 AudioUnit 在播放的時候他會自動取得緩衝區裡面的資料使用。如果是要用來播放其他格式的檔案,那麼我們就必須要擁有其他格式的 Codec 來將檔案解碼成系統所支援的格式。這裡,我們就使用蘋果提供的 Audio File API 來讀取檔案。記住,在這裡我們只有假設我們讀取的是很小的音樂檔案,所以說我們將整個檔案都放到了緩衝區中。事實上,當你的音樂比較龐大的時候,最好是將你解碼的部份分段放入緩衝區中,解碼的動作可能必須要開一個新的執行緒以避免和主程序互衝。

Code:
UInt64 totalPacketCount;
UInt64 fileByteCount;
UInt32 packetSize;
...
OSStatus
OpenAudioFile(AudioFileID * fileID,
                       AudioStreamBasicDescription * fileASBD,
                       const char * filename)

{
OSStatus err = noErr;
Uint32 size;

// 產生路徑,記住檔案路徑是以 UTF8 編碼的
CFURLRef fileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8*)filename, strlen(filename), NO);

// AudioFileOpen 已經不再被支援了
err = AudioFileOpenURL(fileURL, kAudioFileReadPermission, 0, fileID);

// 讀取 Packet 資訊,這個資訊會在 AudioConverter 中使用
// 我們已經確定了 Format Size 了,這裡不再使用 AudioUnitGetPropertyInfo
size = sizeof(AudioStreamBasicDescription);
memset(fileASBD, 0, size);
err = AudioFileGetProperty(*fileID, kAudioFilePropertyDataFormat, &size, fileASBD);
  
size = sizeof(totalPacketCount);
err = AudioFileGetProperty(*fileID, kAudioFilePropertyAudioDataPacketCount, &size, &totalPacketCount);
  
size = sizeof(fileByteCount);
err = AudioFileGetProperty(*fileID, kAudioFilePropertyAudioDataByteCount, &size, &fileByteCount);
  
size = sizeof(packetSize);
err = AudioFileGetProperty(*fileID, kAudioFilePropertyMaximumPacketSize, &size, &packetSize);

return err;
}

int main(int argc, char ** argv)
{
AudioFileID fileID;
UInt32 bytesReturned = 0;
UInt32 packets = totalPacketCount;
char * audioBuffer = malloc(fileByteCount);

...
// 將整個音樂檔案放到記憶體中
err = OpenAudioFile(&fileID, &inputDesc, argv[1]);
err = AudioFileReadPackets(fileID, NO, &butesReturned, NULL, 0, &packets, audioBuffer);
}

接下來,我們就來撰寫確實的 Render Callback。這個 Callback 會在 AudioUnit 需要 Audio Frame 的時候被呼叫。我們在這裡使用 AudioConverter 將讀近來的檔案轉換成系統支援的格式。這裡我們不多寫程式碼,所以我們就只有在 PCM 之間轉換。也就是說,這個程式碼只能夠播放 PCM 的檔案格式:WAV、AIF。

Code:
AudioConverter converter;
AudioConverterNew(&inputDesc, &outputDesc, &converter);

OSStatus MyFileRenderProc(void * inRefCon,
                              AudioUnitRenderActionFlags *inActionFlag,
                              const AudioTimeStamp *timeStamp, UInt32 inBusNumber,
                              UInt32 inNumFrames, AudioBufferList *ioData)
{
    OSStatus err = noErr;
  
    AudioConverterFillComplexBuffer(audioConverter,
                                    MyACComplexInputProc,
                                    0, &inNumFrames, ioData, 0);
  
    return err;
}

OSStatus MyACComplexInputProc(AudioConverterRef inAudioConverter,
                              UInt32 * ioNumberDataPackets,
                              AudioBufferList * ioData,
                              AudioStreamPacketDescription ** ioDataPacketDescription,
                              void * inUserData)
{
    OSStatus err = noErr;
    UInt32 bytesCopied = 0;
  
    ioData->mBuffers[0].mData = NULL;
    ioData->mBuffers[0].mDataByteSize = 0;
  
    if (playedPacketOffset + *ioNumberDataPackets > totalPacketCount)
        *ioNumberDataPackets = totalPacketCount - playedPacketOffset;
  
    if (*ioNumberDataPackets) {
        if (sourceBuffer != NULL) {
            free(sourceBuffer);
            sourceBuffer = NULL;
        }
      
        bytesCopied = *ioNumberDataPackets * packetSize;
        sourceBuffer = calloc(1, bytesCopied);
        memcpy(sourceBuffer, audioBuffer + playedByteOffset, bytesCopied);
      
        playedByteOffset += bytesCopied;
        playedPacketOffset += *ioNumberDataPackets;
      
        ioData->mBuffers[0].mData = sourceBuffer;
        ioData->mBuffers[0].mDataByteSize = bytesCopied;
    }
    else {
        ioData->mBuffers[0].mData = NULL;
        ioData->mBuffers[0].mDataByteSize = 0;
        isPlaying = NO;
        err = noErr;
    }

    return err;
}

以上,是使用 AudioUnit 來播放音樂檔案的簡單程式。如果有可能,我會繼續將學習 CoreAudio 時所學到的東西繼續發佈上來。也請發現有問題的前輩多多指教。

Download Source Code Here.
How to compile: gcc auplaysample.m -framework AudioUnit -framework AudioToolbox -framework CoreService -o AudioPlay

P.S. 話說回來,如果我用 NSSound 的話,上面一大串就剩下兩行了!高階 API 跟低階 API 差距好大啊…
0

Eternal recurrence

| 5/29/2010
Japanese Game "星空のメモリア" OP


歌:橋本みゆき

仰望夜空 流星閃耀
穿越時光 協同願望
遙遠記憶 在小小的山丘上
互相言語 那深刻的未來之夢

牽繞的手 相互分離
立下約定 將永恆不忘

那願望實現的故事 在眼中上映
將兩人結合的棱鏡
你在哪裡 看見這片相同的天空?
我想傳達給你 永恆(Eternal)


幾千星座 隨季轉變
長大成人 我們距離變化
不知何時 無法傳達的言語
從胸中 漸漸離去

回憶中的地方 不曾改變
將遺忘的夢想 描繪出來

你那時所告訴我的光芒
是將兩人結合的棱鏡
那個願望 將傳達到天空的彼方
引導著命運


閃耀的光輝落下的夜晚
將兩人的思緒包圍

那願望實現的故事 在眼中上映
將兩人結合的棱鏡
你在哪裡 看見這片相同的天空?
我想傳達給你 永恆(Eternal)


簡介:
最重要的人,最遙遠的存在。幼小的心靈裡面,萌生的淡淡的愛意。他們有個願望,卻沒能說出口。在此分離,將希望寄託於兩人約定的那顆星星,以及,未來的可能性。

山丘:觀景台
棱鏡:望遠鏡
光芒:七夕星
0

星空的回憶

| 2/13/2010
Japanese Game "星空のメモリア" 夢ED



仰望著那片不變的天空
依然憶起舊時所言的話語
只在那短暫的瞬間 指尖中所傳出
在那遙遠的過去 萌生的 戀情

即便分離 我依然堅信
就猶如這片閃耀的星空般
經過長如永恆的時光 依然不會改變
這股想要傳達給你的思念

我們以交互的雙手 立下了約定
這段感情 絕對會再度相合
尋找著現而即逝 卻又無可替代
耀眼動人的 Shooting Star

閉上雙眼 那臉龐所照映著
是你的身影
捧著隨時都會凋零的花朵
期待著這將會是永恆

懷念的清風之中
回憶就如同他在耳邊的細語
毫無改變的那抹微笑
以及想要傳達給你的思念

不曾忘記 你說過的那一切
那個夜晚 緩緩流動的星河
宛如在夜空中跑動的一道彩虹
架起了橋樑 延續到你的身邊

我想要守護著你的幸福
即便是在虛幻的夢境之中

總有一刻 我們將會重逢
宣洩出來的愛意就在這片佈滿繁星的夜空之下
永遠在心中懷抱著對你的思念

即便分離 也一定會實現
就猶如這片閃耀的星空般
經過長如永恆的時光 依然不會改變
這股想要傳達給你的思念

我們以交互的雙手 立下了約定
這段感情 絕對會再度相合
尋找著現而即逝 卻又無可替代
耀眼動人的 Shooting Star