如何在iOS中添加PCM数据/缓冲区的可播放(如wav,wmv)头?

l3zydbqr  于 2023-01-06  发布在  iOS
关注(0)|答案(4)|浏览(286)

我尝试在原始PCM数据上添加一个wav头,使其可以通过AVAudioPlayer播放。但是我找不到任何解决方案或源代码,在iOS上使用Objective-C/Swift。虽然我找到了this,但它没有正确的答案。
但是我发现了一段代码here,这是在C中,也包含一些问题。wav文件不能正常播放,这是从该代码生成的。
我已经给了我的代码下面,我已经编码到目前为止。

int NumChannels = AUDIO_CHANNELS_PER_FRAME;
short BitsPerSample = AUDIO_BITS_PER_CHANNEL;
int SamplingRate = AUDIO_SAMPLE_RATE;
int numOfSamples = [[NSData dataWithContentsOfFile:filePath] length];

int ByteRate = NumChannels*BitsPerSample*SamplingRate/8;
short BlockAlign = NumChannels*BitsPerSample/8;
int DataSize = NumChannels*numOfSamples*BitsPerSample/8;
int chunkSize = 16;
int totalSize = 36 + DataSize;
short audioFormat = 1;

if((fout = fopen([wavFilePath cStringUsingEncoding:1], "w")) == NULL)
{
    printf("Error opening out file ");
}

fwrite("RIFF", sizeof(char), 4,fout);
fwrite(&totalSize, sizeof(int), 1, fout);
fwrite("WAVE", sizeof(char), 4, fout);
fwrite("fmt ", sizeof(char), 3, fout);
fwrite(&chunkSize, sizeof(int),1,fout);
fwrite(&audioFormat, sizeof(short), 1, fout);
fwrite(&NumChannels, sizeof(short),1,fout);
fwrite(&SamplingRate, sizeof(int), 1, fout);
fwrite(&ByteRate, sizeof(int), 1, fout);
fwrite(&BlockAlign, sizeof(short), 1, fout);
fwrite(&BitsPerSample, sizeof(short), 1, fout);
fwrite("data", sizeof(char), 3, fout);
fwrite(&DataSize, sizeof(int), 1, fout);

文件播放太快,声音失真,只有前10至20(左右)秒播放。我认为,wav头没有正确生成(因为我能够使用AudioUnit/AudioQueue播放相同的PCM数据/缓冲区)。所以我在我的代码中遗漏了什么?任何帮助将不胜感激。
先谢了。

zc0qhyus

zc0qhyus1#

好吧,如果这对别人有帮助,我就回答我自己的问题。经过几天不知疲倦的尝试,下面是一个用Objective-C和C编写的完整函数。它以一个文件路径作为参数,其中包含直接从麦克风捕获的原始PCM数据,并返回一个文件路径,其中包含PCM数据,然后是适当的wav头信息。然后你就可以用AVAudioPlayer或者AVPlayer播放那个文件了。下面是代码...

- (NSURL *) getAndCreatePlayableFileFromPcmData:(NSString *)filePath
{
    NSString *wavFileName = [[filePath lastPathComponent] stringByDeletingPathExtension];
    NSString *wavFileFullName = [NSString stringWithFormat:@"%@.wav",wavFileName];

    [self createFileWithName:wavFileFullName];
    NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *docsDir = [dirPaths objectAtIndex:0];
    NSString *wavFilePath = [docsDir stringByAppendingPathComponent:wavFileFullName];

    NSLog(@"PCM file path : %@",filePath);

    FILE *fout;

    short NumChannels = AUDIO_CHANNELS_PER_FRAME;
    short BitsPerSample = AUDIO_BITS_PER_CHANNEL;
    int SamplingRate = AUDIO_SAMPLE_RATE;
    int numOfSamples = [[NSData dataWithContentsOfFile:filePath] length];

    int ByteRate = NumChannels*BitsPerSample*SamplingRate/8;
    short BlockAlign = NumChannels*BitsPerSample/8;
    int DataSize = NumChannels*numOfSamples*BitsPerSample/8;
    int chunkSize = 16;
    int totalSize = 46 + DataSize;
    short audioFormat = 1;

    if((fout = fopen([wavFilePath cStringUsingEncoding:1], "w")) == NULL)
    {
        printf("Error opening out file ");
    }

    fwrite("RIFF", sizeof(char), 4,fout);
    fwrite(&totalSize, sizeof(int), 1, fout);
    fwrite("WAVE", sizeof(char), 4, fout);
    fwrite("fmt ", sizeof(char), 4, fout);
    fwrite(&chunkSize, sizeof(int),1,fout);
    fwrite(&audioFormat, sizeof(short), 1, fout);
    fwrite(&NumChannels, sizeof(short),1,fout);
    fwrite(&SamplingRate, sizeof(int), 1, fout);
    fwrite(&ByteRate, sizeof(int), 1, fout);
    fwrite(&BlockAlign, sizeof(short), 1, fout);
    fwrite(&BitsPerSample, sizeof(short), 1, fout);
    fwrite("data", sizeof(char), 4, fout);
    fwrite(&DataSize, sizeof(int), 1, fout);

    fclose(fout);

    NSMutableData *pamdata = [NSMutableData dataWithContentsOfFile:filePath];
    NSFileHandle *handle;
    handle = [NSFileHandle fileHandleForUpdatingAtPath:wavFilePath];
    [handle seekToEndOfFile];
    [handle writeData:pamdata];
    [handle closeFile];

    return [NSURL URLWithString:wavFilePath];
}

但该功能仅适用于以下音频设置。

// Audio settings.
#define AUDIO_SAMPLE_RATE 8000
#define AUDIO_FRAMES_PER_PACKET 1
#define AUDIO_CHANNELS_PER_FRAME 1
#define AUDIO_BITS_PER_CHANNEL 16
#define AUDIO_BYTES_PER_PACKET 2
#define AUDIO_BYTES_PER_FRAME 2
tv6aics1

tv6aics12#

非常有帮助的问答,非常感谢。
此快捷版本适用于有需要的人士:

static func createWAV(from pcmFilePath: String, to wavFilePath: String) -> Bool {

    // Make sure that the path does not contain non-ascii characters
    guard let fout = fopen(wavFilePath.cString(using: .ascii), "w") else { return false }

    guard let pcmData = try? Data(contentsOf: URL(fileURLWithPath: pcmFilePath)) else { return false }

    var numChannels: CShort = 1
    let numChannelsInt: CInt = 1
    var bitsPerSample: CShort = 16
    let bitsPerSampleInt: CInt = 16
    var samplingRate: CInt = 16000
    let numOfSamples = CInt(pcmData.count)
    var byteRate = numChannelsInt * bitsPerSampleInt * samplingRate / 8
    var blockAlign = numChannelsInt * bitsPerSampleInt / 8
    var dataSize = numChannelsInt * numOfSamples * bitsPerSampleInt / 8
    var chunkSize: CInt = 16
    var totalSize = 46 + dataSize
    var audioFormat: CShort = 1

    fwrite("RIFF".cString(using: .ascii), MemoryLayout<CChar>.size, 4, fout)
    fwrite(&totalSize, MemoryLayout<CInt>.size, 1, fout)
    fwrite("WAVE".cString(using: .ascii), MemoryLayout<CChar>.size, 4, fout);
    fwrite("fmt ".cString(using: .ascii), MemoryLayout<CChar>.size, 4, fout);
    fwrite(&chunkSize, MemoryLayout<CInt>.size,1,fout);
    fwrite(&audioFormat, MemoryLayout<CShort>.size, 1, fout);
    fwrite(&numChannels, MemoryLayout<CShort>.size,1,fout);
    fwrite(&samplingRate, MemoryLayout<CInt>.size, 1, fout);
    fwrite(&byteRate, MemoryLayout<CInt>.size, 1, fout);
    fwrite(&blockAlign, MemoryLayout<CShort>.size, 1, fout);
    fwrite(&bitsPerSample, MemoryLayout<CShort>.size, 1, fout);
    fwrite("data".cString(using: .ascii), MemoryLayout<CChar>.size, 4, fout);
    fwrite(&dataSize, MemoryLayout<CInt>.size, 1, fout);
    fclose(fout);

    guard let handle = FileHandle(forUpdatingAtPath: wavFilePath) else { return false }

    handle.seekToEndOfFile()
    handle.write(pcmData)
    handle.closeFile()

    return true
}
thtygnil

thtygnil3#

修改自qiz对swift 5的回答

func extractSubchunks(data:Data) -> RiffFile? {
    var data = data
    var chunks = [SubChunk]()
    let position = data.subdata(in: 8..<12)
    let filelengthBytes = data.subdata(in: 4..<8).map { UInt32($0) }
    let filelength: UInt32 = filelengthBytes[0] << 24 + filelengthBytes[1] << 16 + filelengthBytes[2] << 8 + filelengthBytes[3]
    let wave = String(bytes: position, encoding: .utf8) ?? "NoName"
    guard wave == "WAVE" else {
        print("File is \(wave) not WAVE")
        return nil
    }
    data.removeSubrange(0..<12)
    print("Found chunks")
    while data.count != 0{
        let position = data.subdata(in: 0..<4)
        let lengthBytes = data.subdata(in: 4..<8).map { UInt32($0) }
        let length: UInt32 = lengthBytes[0] << 24 + lengthBytes[1] << 16 + lengthBytes[2] << 8 + lengthBytes[3]
        guard let current = String(bytes: position, encoding: .utf8) else{
            return nil
        }
        data.removeSubrange(0..<8)
        let chunkData = data.subdata(in: 0..<Int(length))
        data.removeSubrange(0..<Int(length))
        let subchunk = SubChunk(name: current, size: Int(length), data: chunkData)
        chunks.append(subchunk)
        print(subchunk.debugDescription)
    }
    let riff = RiffFile(size: Int(filelength), subChunks: chunks)
    return riff
}
cczfrluj

cczfrluj4#

这是Swift的一个数据扩展,它返回另一个数据,使用the answer from qiz生成。

extension Data {
    func wavValue: Data? {
        var numChannels: CShort = 1
        let numChannelsInt: CInt = 1
        var bitsPerSample: CShort = 16
        let bitsPerSampleInt: CInt = 16
        var samplingRate: CInt = 44100
        let numOfSamples = CInt(pcmData.count)
        var byteRate = numChannelsInt * bitsPerSampleInt * samplingRate / 8
        var blockAlign = numChannelsInt * bitsPerSampleInt / 8
        var dataSize = numChannelsInt * numOfSamples * bitsPerSampleInt / 8
        var chunkSize: CInt = 16
        var totalSize = 46 + dataSize
        var audioFormat: CShort = 1

        let wavNSData = NSMutableData()
        wavNSData.append("RIFF".cString(using: .ascii) ?? .init(), length: MemoryLayout<CChar>.size * 4)
        wavNSData.append(&totalSize, length: MemoryLayout<CInt>.size)
        wavNSData.append("WAVE".cString(using: .ascii) ?? .init(), length: MemoryLayout<CChar>.size * 4)
        wavNSData.append("fmt ".cString(using: .ascii) ?? .init(), length: MemoryLayout<CChar>.size * 4)
        wavNSData.append(&chunkSize, length: MemoryLayout<CInt>.size)
        wavNSData.append(&audioFormat, length: MemoryLayout<CShort>.size)
        wavNSData.append(&numChannels, length: MemoryLayout<CShort>.size)
        wavNSData.append(&samplingRate, length: MemoryLayout<CInt>.size)
        wavNSData.append(&byteRate, length: MemoryLayout<CInt>.size)
        wavNSData.append(&blockAlign, length: MemoryLayout<CShort>.size)
        wavNSData.append(&bitsPerSample, length: MemoryLayout<CShort>.size)
        wavNSData.append("data".cString(using: .ascii) ?? .init(), length: MemoryLayout<CChar>.size * 4)
        wavNSData.append(&dataSize, length: MemoryLayout<CInt>.size)

        wavNSData.append(self)

        let wavData = Data(referencing: wavNSData)

        return wavData
    } 
}

相关问题