将 1ch 16000Hz PCM 转换为 2ch 44100Hz PCM

Posted

技术标签:

【中文标题】将 1ch 16000Hz PCM 转换为 2ch 44100Hz PCM【英文标题】:Converting 1ch 16000Hz PCM to 2ch 44100Hz PCM 【发布时间】:2014-04-15 09:10:00 【问题描述】:

我需要转换格式为 PCM 音频数据:

Data format:     1 ch,  16000 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer
            no channel layout.
estimated duration: 1.101063 sec
audio bytes: 35234
audio packets: 17617
bit rate: 256000 bits per second
packet size upper bound: 2
maximum packet size: 2
audio data file offset: 44
optimized
source bit depth: I16

到 16 位 2ch(立体声)44100Hz PCM。

我的输入文件以 NSData 的形式出现,理想情况下,如果我最终可以使用 NSData 而不是将输出保存到文件中。我看过很多关于转换不同音频格式的教程和示例,但它们看起来非常复杂,我想知道是否有任何简单的解决方案可以做到这一点。这是我到目前为止尝试过的代码:

-(void)convertAudioToRequiredFormat:(NSData *)data 

AudioFileID refAudioFileID;
ExtAudioFileRef inputFileID;
ExtAudioFileRef outputFileID;

OSStatus result = AudioFileOpenWithCallbacks((__bridge void *)(data), readProc, 0, getSizeProc, 0, kAudioFormatLinearPCM, &refAudioFileID);
if (result != noErr) 
    DLog(@"error reading input audio file");


result = ExtAudioFileWrapAudioFileID(refAudioFileID, false, &inputFileID);
if (result != noErr)
    DLog(@"problem in theAudioFileReaderWithData function Wraping the audio FileID: result code %i \n", (int)result);


AudiostreamBasicDescription clientFormat;
memset(&clientFormat, 0, sizeof(clientFormat));
clientFormat.mFormatID          = kAudioFormatLinearPCM;
clientFormat.mSampleRate        = 16000;
clientFormat.mFramesPerPacket   = 1;
clientFormat.mBytesPerPacket    = 2; //16 bits * 1 channel
clientFormat.mBytesPerFrame     = 2;
clientFormat.mChannelsPerFrame  = 1; //1 channel
clientFormat.mBitsPerChannel    = 16;
clientFormat.mFormatFlags       = kCAFLinearPCMFormatFlagIsLittleEndian;
clientFormat.mReserved          = 0;


AudioStreamBasicDescription outputFormat;
memset(&outputFormat, 0, sizeof(outputFormat));
outputFormat.mFormatID             = kAudioFormatLinearPCM;
outputFormat.mSampleRate           = 44100;
outputFormat.mFramesPerPacket      = 1;  //it is always 1 for PCM
outputFormat.mBytesPerPacket       = 4;  //4 Bytes = 2 * 16 bits
outputFormat.mBytesPerFrame        = 4;
outputFormat.mChannelsPerFrame     = 2;  //2 channels = stereo
outputFormat.mBitsPerChannel       = 16; //16 bits per channel
outputFormat.mFormatFlags          = kCAFLinearPCMFormatFlagIsLittleEndian;
clientFormat.mReserved             = 0;

UInt32 outputFormatSize = sizeof(outputFormat);
result = 0;
result = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outputFormatSize, &outputFormat);
if(result != noErr)
    NSLog(@"could not set the output format with status code %i \n",(int)result);

NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = [docPaths objectAtIndex:0];
NSString *path = [docPath stringByAppendingPathComponent:@"newFormat.wav"];
CFURLRef sourceURL = (__bridge CFURLRef)[[NSURL alloc] initFileURLWithPath:path];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:path]) 
    NSData *content = [NSData dataWithBytes:NULL length:0];
    [fm createFileAtPath:path contents:content attributes:nil];


result      =  0;
result      =  ExtAudioFileCreateWithURL(sourceURL, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &outputFileID);
if(result != noErr)
    NSLog(@"ExtAudioFileCreateWithURL failed for outputFileID with status %i \n", (int)result);

int size = sizeof(clientFormat);
result = 0;
result = ExtAudioFileSetProperty(inputFileID, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat);
if(result != noErr)
    NSLog(@"error on ExtAudioFileSetProperty for input File with result code %i \n", (int)result);
size = sizeof(clientFormat);
result = 0;
result = ExtAudioFileSetProperty(outputFileID, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat);
if(result != noErr)
    NSLog(@"error on ExtAudioFileSetProperty for output File with result code %i \n", (int)result);
int totalFrames = 0;
UInt32 outputFilePacketPosition = 0; //in bytes
UInt32 encodedBytes = 0;
while (1) 
    UInt32 bufferByteSize       = 22050 * 4 * 2;
    char srcBuffer[bufferByteSize];
    UInt32 numFrames            = (bufferByteSize/clientFormat.mBytesPerFrame);
    AudioBufferList fillBufList;
    fillBufList.mNumberBuffers  = 1;
    fillBufList.mBuffers[0].mNumberChannels     = clientFormat.mChannelsPerFrame;
    fillBufList.mBuffers[0].mDataByteSize       = bufferByteSize;
    fillBufList.mBuffers[0].mData               = srcBuffer;
    result = 0;
    result = ExtAudioFileRead(inputFileID, &numFrames, &fillBufList);
    if (result != noErr) 
        NSLog(@"Error on ExtAudioFileRead with result code %i \n", (int)result);
        totalFrames = 0;
        break;
    
    if (!numFrames)
        break;
    totalFrames = totalFrames + numFrames;
    result = 0;
    result = ExtAudioFileWrite(outputFileID,
                               numFrames,
                               &fillBufList);
    if(result!= noErr)
        NSLog(@"ExtAudioFileWrite failed with code %i \n", (int)result);
    
    encodedBytes += numFrames  * clientFormat.mBytesPerFrame;

//Clean up
ExtAudioFileDispose(inputFileID);
ExtAudioFileDispose(outputFileID);
AudioFileClose(refAudioFileID);



static OSStatus readProc(void* clientData, SInt64 position, UInt32 requestCount, void* buffer, UInt32* actualCount)

NSData *inAudioData = (__bridge NSData *) clientData;
size_t dataSize = inAudioData.length;
size_t bytesToRead = 0;
if(position < dataSize) 
    size_t bytesAvailable = dataSize - position;
    bytesToRead = requestCount <= bytesAvailable ? requestCount : bytesAvailable;
    [inAudioData getBytes: buffer range:NSMakeRange(position, bytesToRead)];
 else 
    NSLog(@"data was not read \n");
    bytesToRead = 0;

if(actualCount)
    *actualCount = bytesToRead;
return noErr;



static SInt64 getSizeProc(void* clientData) 
    NSData *inAudioData = (__bridge NSData *) clientData;
    size_t dataSize = inAudioData.length;
    return dataSize;

不幸的是,它不起作用,我在该行中以 EXC_BAD_ACCESS 结束:

result      =  ExtAudioFileCreateWithURL(sourceURL, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &outputFileID);

我不知道是什么导致了该错误(错误的 AudioStreamBasicDescription?)。有人可以帮我解决吗?或者也许有更简单的方法可以将该音频数据转换为所需的 PCM 格式?

【问题讨论】:

【参考方案1】:

一些提示。

1.) AudioFileID refAudioFileID; ExtAudioFileRef 输入文件ID; ExtAudioFileRef 输出文件ID;

^ 用 NULL 初始化指针。

2.) CFURLRef sourceURL = (__bridge CFURLRef)[[NSURL alloc] initFileURLWithPath:path];

^ 做: NSURL *sourceURL = [NSURL fileURLWithPath:path];

3.) NSData *content = [NSData dataWithBytes:NULL 长度:0];

^ 使用: NSData *content = [NSData 数据];

4.) 结果 = ExtAudioFileCreateWithURL(sourceURL, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &outputFileID);

^ 看起来好像您使用了错误的 kAudioFileType:尝试 kAudioFileAIFFType 或 kAudioFileWAVEType

我的两分钱。祝你好运!

【讨论】:

以上是关于将 1ch 16000Hz PCM 转换为 2ch 44100Hz PCM的主要内容,如果未能解决你的问题,请参考以下文章

将原始PCM数据转换为RIFF WAV

使用 NAudio 将 PCM S16 LE (s16l) 转换为 GSM610

使用带有 C# 的 MediaTranscoder 将 PCM 音频转码为 MP3

PCM调节器原理?

将 PCM 16 位音频转换为 PCM 8 位

PCM音量控制