Öğretici: Ses Kodunu Çözme
Bu öğreticide, Kaynak Okuyucu kullanarak bir medya dosyasından sesin kodunu çözme ve sesi WAVE dosyasına yazma işlemi gösterilmektedir. Öğretici, Ses Klibi örneğini temel alır.
- Genel Bakış
- Üst Bilgi ve Kitaplık Dosyaları
- wmain'i Uygula
- WAVE Dosyasını Yaz
- Kaynak Okuyucu Yapılandırma
- WAVE Dosya Üst Bilgisini Yazma
- En Büyük Veri Boyutunu Hesaplama
- Sesi Çözme
- Dosya Üst Bilgisini Sonlandır
- İlgili konular
Genel bakış
Bu eğitimde, iki komut satırı argümanı alan bir konsol uygulaması oluşturacaksınız: Ses akışını içeren giriş dosyasının adı ve çıkış dosyasının adı. Uygulama, giriş dosyasından beş saniyelik ses verilerini okur ve sesi çıkış dosyasına WAVE verileri olarak yazar.
Kodu çözülen ses verilerini almak için uygulama kaynak okuyucu nesnesini kullanır. Kaynak okuyucu, IMFSourceReader arabirimini kullanıma sunar. Çözülen sesi WAVE dosyasına yazmak için uygulamalar Windows G/Ç işlevlerini kullanır. Aşağıdaki görüntüde bu işlem gösterilmektedir.
Kaynak dosyadan ses verilerini alan kaynak okuyucuyu gösteren
En basit haliyle wave dosyası aşağıdaki yapıya sahiptir:
Veri tipi | Boyut (Bayt) | Değer |
---|---|---|
FOURCC | 4 | RIFF |
DWORD | 4 | İlk 8 bayt dahil olmak üzere toplam dosya boyutu |
FOURCC | 4 | 'WAVE' |
FOURCC | 4 | 'fmt' |
DWORD | 4 | Aşağıdaki WAVEFORMATEX verilerinin boyutu. |
WAVEFORMATEX | Değişir | Ses formatı başlığı. |
FOURCC | 4 | veri |
DWORD | 4 | Ses verilerinin boyutu. |
BYTE[] | Değişir | Ses verileri. |
Not
FOURCC, dört ASCII karakteri birleştirerek oluşturan bir DWORD'dur.
Bu temel yapı, bu öğreticinin kapsamı dışında olan dosya meta verileri ve diğer bilgiler eklenerek genişletilebilir.
Başlık ve Kitaplık Dosyaları
Projenize aşağıdaki üst bilgi dosyalarını ekleyin:
#define WINVER _WIN32_WINNT_WIN7
#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <stdio.h>
#include <mferror.h>
Aşağıdaki kitaplıklara bağlantı:
- mfplat.lib
- mfreadwrite.lib
- mfuuid.lib
wmain'i gerçekleştirin
Aşağıdaki kod, uygulamanın giriş noktası işlevini gösterir.
int wmain(int argc, wchar_t* argv[])
{
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
if (argc != 3)
{
printf("arguments: input_file output_file.wav\n");
return 1;
}
const WCHAR *wszSourceFile = argv[1];
const WCHAR *wszTargetFile = argv[2];
const LONG MAX_AUDIO_DURATION_MSEC = 5000; // 5 seconds
HRESULT hr = S_OK;
IMFSourceReader *pReader = NULL;
HANDLE hFile = INVALID_HANDLE_VALUE;
// Initialize the COM library.
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
// Initialize the Media Foundation platform.
if (SUCCEEDED(hr))
{
hr = MFStartup(MF_VERSION);
}
// Create the source reader to read the input file.
if (SUCCEEDED(hr))
{
hr = MFCreateSourceReaderFromURL(wszSourceFile, NULL, &pReader);
if (FAILED(hr))
{
printf("Error opening input file: %S\n", wszSourceFile, hr);
}
}
// Open the output file for writing.
if (SUCCEEDED(hr))
{
hFile = CreateFile(wszTargetFile, GENERIC_WRITE, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
printf("Cannot create output file: %S\n", wszTargetFile, hr);
}
}
// Write the WAVE file.
if (SUCCEEDED(hr))
{
hr = WriteWaveFile(pReader, hFile, MAX_AUDIO_DURATION_MSEC);
}
if (FAILED(hr))
{
printf("Failed, hr = 0x%X\n", hr);
}
// Clean up.
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
}
SafeRelease(&pReader);
MFShutdown();
CoUninitialize();
return SUCCEEDED(hr) ? 0 : 1;
};
Bu işlev aşağıdakileri yapar:
- COM kitaplığını başlatmak için CoInitializeExçağırır.
- 'yi çağırarak MFStartup Media Foundation platformunu başlatır.
- Kaynak okuyucuyu oluşturmak için MFCreateSourceReaderFromURLçağırır. Bu işlev giriş dosyasının adını alır ve bir IMFSourceReader arabirim işaretçisi alır.
- Bir dosya tanıtıcısı döndüren CreateFile işlevini çağırarak çıkış dosyasını oluşturur.
- Uygulama tanımlı WriteWavFile işlevini çağırır. Bu işlev sesin kodunu çözer ve WAVE dosyasını yazar.
- IMFSourceReader işaretçisini ve dosya tanıtıcısını serbest bırakır.
- MFShutdown'u çağırarak Media Foundation platformunu kapatır .
- COM kitaplığını serbest bırakmak için CoUninitialize çağırır.
WAVE Dosyasını Yazma
İşin çoğu, wmain
'den çağrılan WriteWavFile
işlevinde gerçekleşir.
//-------------------------------------------------------------------
// WriteWaveFile
//
// Writes a WAVE file by getting audio data from the source reader.
//
//-------------------------------------------------------------------
HRESULT WriteWaveFile(
IMFSourceReader *pReader, // Pointer to the source reader.
HANDLE hFile, // Handle to the output file.
LONG msecAudioData // Maximum amount of audio data to write, in msec.
)
{
HRESULT hr = S_OK;
DWORD cbHeader = 0; // Size of the WAVE file header, in bytes.
DWORD cbAudioData = 0; // Total bytes of PCM audio data written to the file.
DWORD cbMaxAudioData = 0;
IMFMediaType *pAudioType = NULL; // Represents the PCM audio format.
// Configure the source reader to get uncompressed PCM audio from the source file.
hr = ConfigureAudioStream(pReader, &pAudioType);
// Write the WAVE file header.
if (SUCCEEDED(hr))
{
hr = WriteWaveHeader(hFile, pAudioType, &cbHeader);
}
// Calculate the maximum amount of audio to decode, in bytes.
if (SUCCEEDED(hr))
{
cbMaxAudioData = CalculateMaxAudioDataSize(pAudioType, cbHeader, msecAudioData);
// Decode audio data to the file.
hr = WriteWaveData(hFile, pReader, cbMaxAudioData, &cbAudioData);
}
// Fix up the RIFF headers with the correct sizes.
if (SUCCEEDED(hr))
{
hr = FixUpChunkSizes(hFile, cbHeader, cbAudioData);
}
SafeRelease(&pAudioType);
return hr;
}
Bu işlev bir dizi diğer uygulama tanımlı işlevi çağırır:
- ConfigureAudioStream işlevi kaynak okuyucuyu başlatır. Bu işlev örnek hızı, kanal sayısı ve bit derinliği (örnek başına bit) dahil olmak üzere kod çözme ses biçiminin açıklamasını almak için kullanılan IMFMediaType arabirimine yönelik bir işaretçi alır.
- WriteWaveHeader fonksiyonu, üst bilgi ve 'data' öbeğinin başlangıcı da dahil olmak üzere WAVE dosyasının başlangıç kısmını yazar.
- CalculateMaxAudioDataSize işlevi, dosyaya yazacak en fazla ses miktarını bayt cinsinden hesaplar.
- WriteWaveData işlevi, PCM ses verilerini dosyaya yazar.
- FixUpChunkSizes işlevi, WAVE dosyasındaki 'RIFF' ve 'data' ifadelerinden sonra görünen FOURCC ve dosya boyutu bilgilerini yazar. (Bu değerler,
WriteWaveData
tamamlanana kadar bilinmez.)
Bu işlevler, bu öğreticinin kalan bölümlerinde gösterilir.
Kaynak Okuyucuyu Yapılandırma
ConfigureAudioStream
işlevi, kaynak okuyucuyu kaynak dosyadaki ses akışının kodunu çözecek şekilde yapılandırıyor. Ayrıca kodu çözülen sesin biçimi hakkında bilgi döndürür.
Media Foundation'da medya biçimleri, medya türü nesneler kullanılarak açıklanır. Medya türü nesnesi, IMFAttributes arabirimini devralan IMFMediaType arabirimini kullanıma sunar. Temel olarak, medya türü biçimi açıklayan bir özellik koleksiyonudur. Daha fazla bilgi için bkz. Medya Türleri.
//-------------------------------------------------------------------
// ConfigureAudioStream
//
// Selects an audio stream from the source file, and configures the
// stream to deliver decoded PCM audio.
//-------------------------------------------------------------------
HRESULT ConfigureAudioStream(
IMFSourceReader *pReader, // Pointer to the source reader.
IMFMediaType **ppPCMAudio // Receives the audio format.
)
{
IMFMediaType *pUncompressedAudioType = NULL;
IMFMediaType *pPartialType = NULL;
// Select the first audio stream, and deselect all other streams.
HRESULT hr = pReader->SetStreamSelection(
(DWORD)MF_SOURCE_READER_ALL_STREAMS, FALSE);
if (SUCCEEDED(hr))
{
hr = pReader->SetStreamSelection(
(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE);
}
// Create a partial media type that specifies uncompressed PCM audio.
hr = MFCreateMediaType(&pPartialType);
if (SUCCEEDED(hr))
{
hr = pPartialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
}
if (SUCCEEDED(hr))
{
hr = pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
}
// Set this type on the source reader. The source reader will
// load the necessary decoder.
if (SUCCEEDED(hr))
{
hr = pReader->SetCurrentMediaType(
(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
NULL, pPartialType);
}
// Get the complete uncompressed format.
if (SUCCEEDED(hr))
{
hr = pReader->GetCurrentMediaType(
(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
&pUncompressedAudioType);
}
// Ensure the stream is selected.
if (SUCCEEDED(hr))
{
hr = pReader->SetStreamSelection(
(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
TRUE);
}
// Return the PCM format to the caller.
if (SUCCEEDED(hr))
{
*ppPCMAudio = pUncompressedAudioType;
(*ppPCMAudio)->AddRef();
}
SafeRelease(&pUncompressedAudioType);
SafeRelease(&pPartialType);
return hr;
}
ConfigureAudioStream
işlevi aşağıdakileri yapar:
- Ses akışını seçmek ve diğer tüm akışların seçimini kaldırmak için IMFSourceReader::SetStreamSelection yöntemini çağırır. Bu adım, kaynak okuyucunun uygulamanın kullanmadığı video çerçevelerini tutmasını engellediği için performansı artırabilir.
-
, PCM sesini belirten kısmi medya türünü oluşturur. İşlev, kısmi türü bu şekilde oluşturur:
- Boş bir medya türü nesnesi oluşturmak için MFCreateMediaTypeçağırır.
- MF_MT_MAJOR_TYPE özniteliğini MFMediaType_Audioolarak ayarlar.
- MF_MT_SUBTYPE özniteliğini MFAudioFormat_PCMolarak ayarlar.
- Kaynak okuyucuda kısmi türü ayarlamak için IMFSourceReader::SetCurrentMediaTypeçağırır. Kaynak dosya kodlanmış ses içeriyorsa, kaynak okuyucu gerekli ses kod çözücüsün otomatik olarak yüklenmesini sağlar.
- Gerçek PCM medya türünü almak için IMFSourceReader::GetCurrentMediaType çağırır. Bu yöntem, ses örneği hızı ve kanal sayısı gibi tüm biçim ayrıntılarının doldurulduğu bir medya türü döndürür.
- Ses akışını etkinleştirmek için IMFSourceReader::SetStreamSelectionçağırır.
WAVE Dosya Üst Bilgisini Yazma
WriteWaveHeader
işlevi WAVE dosya üst bilgisini yazar.
Bu işlevden çağrılan tek Media Foundation API'sinin MFCreateWaveFormatExFromMFMediaTypeolması, medya türünü WAVEFORMATEX yapısına dönüştürür.
//-------------------------------------------------------------------
// WriteWaveHeader
//
// Write the WAVE file header.
//
// Note: This function writes placeholder values for the file size
// and data size, as these values will need to be filled in later.
//-------------------------------------------------------------------
HRESULT WriteWaveHeader(
HANDLE hFile, // Output file.
IMFMediaType *pMediaType, // PCM audio format.
DWORD *pcbWritten // Receives the size of the header.
)
{
HRESULT hr = S_OK;
UINT32 cbFormat = 0;
WAVEFORMATEX *pWav = NULL;
*pcbWritten = 0;
// Convert the PCM audio format into a WAVEFORMATEX structure.
hr = MFCreateWaveFormatExFromMFMediaType(pMediaType, &pWav, &cbFormat);
// Write the 'RIFF' header and the start of the 'fmt ' chunk.
if (SUCCEEDED(hr))
{
DWORD header[] = {
// RIFF header
FCC('RIFF'),
0,
FCC('WAVE'),
// Start of 'fmt ' chunk
FCC('fmt '),
cbFormat
};
DWORD dataHeader[] = { FCC('data'), 0 };
hr = WriteToFile(hFile, header, sizeof(header));
// Write the WAVEFORMATEX structure.
if (SUCCEEDED(hr))
{
hr = WriteToFile(hFile, pWav, cbFormat);
}
// Write the start of the 'data' chunk
if (SUCCEEDED(hr))
{
hr = WriteToFile(hFile, dataHeader, sizeof(dataHeader));
}
if (SUCCEEDED(hr))
{
*pcbWritten = sizeof(header) + cbFormat + sizeof(dataHeader);
}
}
CoTaskMemFree(pWav);
return hr;
}
WriteToFile
işlevi, Windows WriteFile işlevini sarmalayan ve bir HRESULT değeri döndüren basit bir yardımcı işlevdir.
//-------------------------------------------------------------------
//
// Writes a block of data to a file
//
// hFile: Handle to the file.
// p: Pointer to the buffer to write.
// cb: Size of the buffer, in bytes.
//
//-------------------------------------------------------------------
HRESULT WriteToFile(HANDLE hFile, void* p, DWORD cb)
{
DWORD cbWritten = 0;
HRESULT hr = S_OK;
BOOL bResult = WriteFile(hFile, p, cb, &cbWritten, NULL);
if (!bResult)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
return hr;
}
Maksimum Veri Boyutunu Hesaplama
Dosya boyutu dosya üst bilgisinde 4 baytlık bir değer olarak depolandığından, WAVE dosyası en büyük 0xFFFFFFFF bayt boyutuyla (yaklaşık 4 GB) sınırlıdır. Bu değer, dosya üst bilgisinin boyutunu içerir. PCM sesinin bit hızı sabittir, bu nedenle ses biçiminden aşağıdaki gibi maksimum veri boyutunu hesaplayabilirsiniz:
//-------------------------------------------------------------------
// CalculateMaxAudioDataSize
//
// Calculates how much audio to write to the WAVE file, given the
// audio format and the maximum duration of the WAVE file.
//-------------------------------------------------------------------
DWORD CalculateMaxAudioDataSize(
IMFMediaType *pAudioType, // The PCM audio format.
DWORD cbHeader, // The size of the WAVE file header.
DWORD msecAudioData // Maximum duration, in milliseconds.
)
{
UINT32 cbBlockSize = 0; // Audio frame size, in bytes.
UINT32 cbBytesPerSecond = 0; // Bytes per second.
// Get the audio block size and number of bytes/second from the audio format.
cbBlockSize = MFGetAttributeUINT32(pAudioType, MF_MT_AUDIO_BLOCK_ALIGNMENT, 0);
cbBytesPerSecond = MFGetAttributeUINT32(pAudioType, MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 0);
// Calculate the maximum amount of audio data to write.
// This value equals (duration in seconds x bytes/second), but cannot
// exceed the maximum size of the data chunk in the WAVE file.
// Size of the desired audio clip in bytes:
DWORD cbAudioClipSize = (DWORD)MulDiv(cbBytesPerSecond, msecAudioData, 1000);
// Largest possible size of the data chunk:
DWORD cbMaxSize = MAXDWORD - cbHeader;
// Maximum size altogether.
cbAudioClipSize = min(cbAudioClipSize, cbMaxSize);
// Round to the audio block size, so that we do not write a partial audio frame.
cbAudioClipSize = (cbAudioClipSize / cbBlockSize) * cbBlockSize;
return cbAudioClipSize;
}
Kısmi ses çerçevelerini önlemek için boyut, MF_MT_AUDIO_BLOCK_ALIGNMENT özniteliğinde depolanan blok hizalamasına yuvarlanır.
Ses kodunu çözme
WriteWaveData
işlevi, kod çözülen sesi kaynak dosyadan okur ve WAVE dosyasına yazar.
//-------------------------------------------------------------------
// WriteWaveData
//
// Decodes PCM audio data from the source file and writes it to
// the WAVE file.
//-------------------------------------------------------------------
HRESULT WriteWaveData(
HANDLE hFile, // Output file.
IMFSourceReader *pReader, // Source reader.
DWORD cbMaxAudioData, // Maximum amount of audio data (bytes).
DWORD *pcbDataWritten // Receives the amount of data written.
)
{
HRESULT hr = S_OK;
DWORD cbAudioData = 0;
DWORD cbBuffer = 0;
BYTE *pAudioData = NULL;
IMFSample *pSample = NULL;
IMFMediaBuffer *pBuffer = NULL;
// Get audio samples from the source reader.
while (true)
{
DWORD dwFlags = 0;
// Read the next sample.
hr = pReader->ReadSample(
(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
0, NULL, &dwFlags, NULL, &pSample );
if (FAILED(hr)) { break; }
if (dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
{
printf("Type change - not supported by WAVE file format.\n");
break;
}
if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM)
{
printf("End of input file.\n");
break;
}
if (pSample == NULL)
{
printf("No sample\n");
continue;
}
// Get a pointer to the audio data in the sample.
hr = pSample->ConvertToContiguousBuffer(&pBuffer);
if (FAILED(hr)) { break; }
hr = pBuffer->Lock(&pAudioData, NULL, &cbBuffer);
if (FAILED(hr)) { break; }
// Make sure not to exceed the specified maximum size.
if (cbMaxAudioData - cbAudioData < cbBuffer)
{
cbBuffer = cbMaxAudioData - cbAudioData;
}
// Write this data to the output file.
hr = WriteToFile(hFile, pAudioData, cbBuffer);
if (FAILED(hr)) { break; }
// Unlock the buffer.
hr = pBuffer->Unlock();
pAudioData = NULL;
if (FAILED(hr)) { break; }
// Update running total of audio data.
cbAudioData += cbBuffer;
if (cbAudioData >= cbMaxAudioData)
{
break;
}
SafeRelease(&pSample);
SafeRelease(&pBuffer);
}
if (SUCCEEDED(hr))
{
printf("Wrote %d bytes of audio data.\n", cbAudioData);
*pcbDataWritten = cbAudioData;
}
if (pAudioData)
{
pBuffer->Unlock();
}
SafeRelease(&pBuffer);
SafeRelease(&pSample);
return hr;
}
WriteWaveData
işlevi bir döngüde aşağıdakileri yapar:
- Kaynak dosyadan ses okumak için IMFSourceReader::ReadSampleçağırır. dwFlags parametresi, MF_SOURCE_READER_FLAG numaralandırmasından bit düzeyinde VEYA bayrak alır. pSample parametresi, ses verilerine erişmek için kullanılan IMFSample arabirimine yönelik bir işaretçi alır. Bazı durumlarda ReadSample çağrısı veri oluşturmaz, bu durumda IMFSample işaretçisi NULLolur.
-
aşağıdaki bayraklar için dwFlags'i denetler.
- MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED. Bu bayrak, kaynak dosyada biçim değişikliğini gösterir. WAVE dosyaları biçim değişikliklerini desteklemez.
- MF_SOURCE_READERF_ENDOFSTREAM. Bu bayrak, akışın sonunu gösterir.
- Bir arabellek nesnesine işaretçi almak için IMFSample::ConvertToContiguousBuffer çağırır.
- Bellek arabelleğine işaretçi almak için IMFMediaBuffer::Lock çağırır.
- Ses verilerini çıkış dosyasına yazar.
- Arabellek nesnesinin kilidini açmak için IMFMediaBuffer::Unlock çağrısı yapılır.
Aşağıdakilerden herhangi biri gerçekleştiğinde işlev döngüden çıkar:
- Akış biçimi değişir.
- Akışın sonuna ulaşıldı.
- Çıkış dosyasına maksimum ses verisi miktarı yazılır.
- Bir hata oluşur.
Dosya Üst Bilgisini Tamamla
Önceki işlev tamamlanana kadar WAVE üst bilgisinde depolanan boyut değerleri bilinmez.
FixUpChunkSizes
şu değerleri doldurur:
//-------------------------------------------------------------------
// FixUpChunkSizes
//
// Writes the file-size information into the WAVE file header.
//
// WAVE files use the RIFF file format. Each RIFF chunk has a data
// size, and the RIFF header has a total file size.
//-------------------------------------------------------------------
HRESULT FixUpChunkSizes(
HANDLE hFile, // Output file.
DWORD cbHeader, // Size of the 'fmt ' chuck.
DWORD cbAudioData // Size of the 'data' chunk.
)
{
HRESULT hr = S_OK;
LARGE_INTEGER ll;
ll.QuadPart = cbHeader - sizeof(DWORD);
if (0 == SetFilePointerEx(hFile, ll, NULL, FILE_BEGIN))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
// Write the data size.
if (SUCCEEDED(hr))
{
hr = WriteToFile(hFile, &cbAudioData, sizeof(cbAudioData));
}
if (SUCCEEDED(hr))
{
// Write the file size.
ll.QuadPart = sizeof(FOURCC);
if (0 == SetFilePointerEx(hFile, ll, NULL, FILE_BEGIN))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
if (SUCCEEDED(hr))
{
DWORD cbRiffFileSize = cbHeader + cbAudioData - 8;
// NOTE: The "size" field in the RIFF header does not include
// the first 8 bytes of the file. (That is, the size of the
// data that appears after the size field.)
hr = WriteToFile(hFile, &cbRiffFileSize, sizeof(cbRiffFileSize));
}
return hr;
}
İlgili konular