在 iOS 上使用 OpenAL 进行声音捕捉

2023-12-25

我正在尝试使用 OpenAL 在 iOS 上进行声音捕获(我正在编写一个跨平台库,这就是为什么我避免使用特定于 iOS 的声音录制方式)。 开箱即用的 OpenAL 捕获不起作用,但存在一个已知的解决方法:在开始捕获之前打开输出上下文 http://opensource.creative.com/pipermail/openal-devel/2011-September/005853.html。这个解决方案在 iOS 5.0 上对我有用。

然而,在 iOS 5.1.1 上,该解决方法仅对我尝试记录的第一个样本有帮助。 (在开始捕获之前,我将 AudioSession 切换为 PlayAndRecord,并打开默认输出设备。录制样本后,我关闭设备并将会话切换回原来的状态。) 对于第二个示例,重新打开输出上下文没有帮助,并且没有捕获任何声音。

有没有已知的方法来处理这个问题?

// Here's what I do before starting the recording
oldAudioSessionCategory = [audioSession category];
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[audioSession setActive:YES error:nil];
// We need to have an active context. If there is none, create one.
if (!alcGetCurrentContext()) {
    outputDevice = alcOpenDevice(NULL);
    outputContext = alcCreateContext(outputDevice, NULL);
    alcMakeContextCurrent(outputContext);
}

// Capture itself
inputDevice = alcCaptureOpenDevice(NULL, frequency, FORMAT, bufferSize);
....
alcCaptureCloseDevice(inputDevice);

// Restoring the audio state to whatever it had been before capture
if (outputContext) {
    alcDestroyContext(outputContext);
    alcCloseDevice(outputDevice);
}
[[AVAudioSession sharedInstance] setCategory:oldAudioSessionCategory 
                                 error:nil];

这是我用来模拟捕获扩展的代码。 一些评论:

  1. 在整个项目中,OpenKD 用于线程原语等。您可能需要替换这些调用。
  2. 我必须在开始捕获时克服延迟。因此,我不断地读取声音输入,并在不需要时将其丢弃。 (提出了这样的解决方案,例如,here https://stackoverflow.com/questions/10141526/audiooutputunitstart-very-slow.) 反过来,这需要捕获 onResignActive 通知,以便释放对麦克风的控制。您可能想也可能不想使用这样的拼凑。
  3. 代替alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, 1, &res),我必须定义一个单独的函数,alcGetAvailableSamples.

简而言之,这段代码不太可能按原样在您的项目中使用,但希望您可以 根据您的需要进行调整。

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <KD/kd.h>
#include <AL/al.h>
#include <AL/alc.h>

#include <AudioToolbox/AudioToolbox.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

#include "KD/kdext.h"

struct InputDeviceData {
    int id;
    KDThreadMutex *mutex;
    AudioUnit audioUnit;
    int nChannels;
    int frequency;
    ALCenum format;
    int sampleSize;
    uint8_t *buf;
    size_t bufSize;    // in bytes
    size_t bufFilledBytes;  // in bytes
    bool started;
};

static struct InputDeviceData *cachedInData = NULL;

static OSStatus renderCallback (void                        *inRefCon,
                                AudioUnitRenderActionFlags  *ioActionFlags,
                                const AudioTimeStamp        *inTimeStamp,
                                UInt32                      inBusNumber,
                                UInt32                      inNumberFrames,
                                AudioBufferList             *ioData);
static AudioUnit getAudioUnit();
static void setupNotifications();
static void destroyCachedInData();
static struct InputDeviceData *setupCachedInData(AudioUnit audioUnit, ALCuint frequency, ALCenum format, ALCsizei bufferSizeInSamples);
static struct InputDeviceData *getInputDeviceData(AudioUnit audioUnit, ALCuint frequency, ALCenum format, ALCsizei bufferSizeInSamples);

/** I only have to use NSNotificationCenter instead of CFNotificationCenter
 *  because there is no published name for WillResignActive/WillBecomeActive
 *  notifications in CoreFoundation.
 */
@interface ALCNotificationObserver : NSObject
- (void)onResignActive;
@end
@implementation ALCNotificationObserver
- (void)onResignActive {
    destroyCachedInData();
}
@end

static void setupNotifications() {
    static ALCNotificationObserver *observer = NULL;
    if (!observer) {
        observer = [[ALCNotificationObserver alloc] init];
        [[NSNotificationCenter defaultCenter] addObserver:observer selector:@selector(onResignActive) name:UIApplicationWillResignActiveNotification object:nil];
    }
}

static OSStatus renderCallback (void                        *inRefCon,
                                AudioUnitRenderActionFlags  *ioActionFlags,
                                const AudioTimeStamp        *inTimeStamp,
                                UInt32                      inBusNumber,
                                UInt32                      inNumberFrames,
                                AudioBufferList             *ioData) {
    struct InputDeviceData *inData = (struct InputDeviceData*)inRefCon;

    kdThreadMutexLock(inData->mutex);
    size_t bytesToRender = inNumberFrames * inData->sampleSize;
    if (bytesToRender + inData->bufFilledBytes <= inData->bufSize) {
        OSStatus status;
        struct AudioBufferList audioBufferList; // 1 buffer is declared inside the structure itself.
        audioBufferList.mNumberBuffers = 1;
        audioBufferList.mBuffers[0].mNumberChannels = inData->nChannels;
        audioBufferList.mBuffers[0].mDataByteSize = bytesToRender;
        audioBufferList.mBuffers[0].mData = inData->buf + inData->bufFilledBytes;
        status = AudioUnitRender(inData->audioUnit, 
                                 ioActionFlags, 
                                 inTimeStamp, 
                                 inBusNumber, 
                                 inNumberFrames, 
                                 &audioBufferList);
        if (inData->started) {
            inData->bufFilledBytes += bytesToRender;
        }
    } else {
        kdLogFormatMessage("%s: buffer overflow", __FUNCTION__);
    }
    kdThreadMutexUnlock(inData->mutex);

    return 0;
}

static AudioUnit getAudioUnit() {
    static AudioUnit audioUnit = NULL;

    if (!audioUnit) {
        AudioComponentDescription ioUnitDescription;

        ioUnitDescription.componentType          = kAudioUnitType_Output;
        ioUnitDescription.componentSubType       = kAudioUnitSubType_VoiceProcessingIO;
        ioUnitDescription.componentManufacturer  = kAudioUnitManufacturer_Apple;
        ioUnitDescription.componentFlags         = 0;
        ioUnitDescription.componentFlagsMask     = 0;

        AudioComponent foundIoUnitReference = AudioComponentFindNext(NULL,
                                                                     &ioUnitDescription);
        AudioComponentInstanceNew(foundIoUnitReference,
                                  &audioUnit);

        if (audioUnit == NULL) {
            kdLogMessage("Could not obtain AudioUnit");
        }
    }

    return audioUnit;
}

static void destroyCachedInData() {
    OSStatus status;
    if (cachedInData) {
        status = AudioOutputUnitStop(cachedInData->audioUnit);
        status = AudioUnitUninitialize(cachedInData->audioUnit);
        free(cachedInData->buf);
        kdThreadMutexFree(cachedInData->mutex);
        free(cachedInData);
        cachedInData = NULL;
    }
}

static struct InputDeviceData *setupCachedInData(AudioUnit audioUnit, ALCuint frequency, ALCenum format, ALCsizei bufferSizeInSamples) {
    static int idCount = 0;
    OSStatus status;
    int bytesPerFrame = (format == AL_FORMAT_MONO8) ? 1 :
                        (format == AL_FORMAT_MONO16) ? 2 :
                        (format == AL_FORMAT_STEREO8) ? 2 :
                        (format == AL_FORMAT_STEREO16) ? 4 : -1;
    int channelsPerFrame = (format == AL_FORMAT_MONO8) ? 1 :
                           (format == AL_FORMAT_MONO16) ? 1 :
                           (format == AL_FORMAT_STEREO8) ? 2 :
                           (format == AL_FORMAT_STEREO16) ? 2 : -1;
    int bitsPerChannel = (format == AL_FORMAT_MONO8) ? 8 :
                         (format == AL_FORMAT_MONO16) ? 16 :
                         (format == AL_FORMAT_STEREO8) ? 8 :
                         (format == AL_FORMAT_STEREO16) ? 16 : -1;

    cachedInData = malloc(sizeof(struct InputDeviceData));
    cachedInData->id = ++idCount;
    cachedInData->format = format;
    cachedInData->frequency = frequency;
    cachedInData->mutex = kdThreadMutexCreate(NULL);
    cachedInData->audioUnit = audioUnit;
    cachedInData->nChannels = channelsPerFrame;
    cachedInData->sampleSize = bytesPerFrame;
    cachedInData->bufSize = bufferSizeInSamples * bytesPerFrame;
    cachedInData->buf = malloc(cachedInData->bufSize);
    cachedInData->bufFilledBytes = 0;
    cachedInData->started = FALSE;

    UInt32 enableOutput        = 1;    // to enable output
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioOutputUnitProperty_EnableIO,
                                  kAudioUnitScope_Input,
                                  1,
                                  &enableOutput, sizeof(enableOutput));

    struct AudioStreamBasicDescription basicDescription;
    basicDescription.mSampleRate = (Float64)frequency;
    basicDescription.mFormatID = kAudioFormatLinearPCM;
    basicDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    basicDescription.mBytesPerPacket = bytesPerFrame;
    basicDescription.mFramesPerPacket = 1;
    basicDescription.mBytesPerFrame = bytesPerFrame;
    basicDescription.mChannelsPerFrame = channelsPerFrame;
    basicDescription.mBitsPerChannel = bitsPerChannel;
    basicDescription.mReserved = 0;

    status = AudioUnitSetProperty(audioUnit, 
                                  kAudioUnitProperty_StreamFormat, // property key 
                                  kAudioUnitScope_Output,        // scope
                                  1,                             // 1 is output
                                  &basicDescription, sizeof(basicDescription));      // value

    AURenderCallbackStruct renderCallbackStruct;
    renderCallbackStruct.inputProc = renderCallback;
    renderCallbackStruct.inputProcRefCon = cachedInData;
    status = AudioUnitSetProperty(audioUnit, 
                                  kAudioOutputUnitProperty_SetInputCallback, // property key 
                                  kAudioUnitScope_Output,        // scope
                                  1,                             // 1 is output
                                  &renderCallbackStruct, sizeof(renderCallbackStruct));      // value

    status = AudioOutputUnitStart(cachedInData->audioUnit);

    return cachedInData;
}

static struct InputDeviceData *getInputDeviceData(AudioUnit audioUnit, ALCuint frequency, ALCenum format, ALCsizei bufferSizeInSamples) {
    if (cachedInData && 
        (cachedInData->frequency != frequency ||
         cachedInData->format != format ||
         cachedInData->bufSize / cachedInData->sampleSize != bufferSizeInSamples)) {
            kdAssert(!cachedInData->started);
            destroyCachedInData();
        }
    if (!cachedInData) {
        setupCachedInData(audioUnit, frequency, format, bufferSizeInSamples);
        setupNotifications();
    }

    return cachedInData;
}


ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersizeInSamples) {
    kdAssert(devicename == NULL);    

    AudioUnit audioUnit = getAudioUnit();
    struct InputDeviceData *res = getInputDeviceData(audioUnit, frequency, format, buffersizeInSamples);
    return (ALCdevice*)res->id;
}

ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) {
    alcCaptureStop(device);
    return true;
}

ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) {
    if (!cachedInData || (int)device != cachedInData->id) {
        // may happen after the app loses and regains active status.
        kdLogFormatMessage("Attempt to start a stale AL capture device");
        return;
    }
    cachedInData->started = TRUE;
}

ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) {
    if (!cachedInData || (int)device != cachedInData->id) {
        // may happen after the app loses and regains active status.
        kdLogFormatMessage("Attempt to stop a stale AL capture device");
        return;
    }
    cachedInData->started = FALSE;
}

ALC_API ALCint ALC_APIENTRY alcGetAvailableSamples(ALCdevice *device) {
    if (!cachedInData || (int)device != cachedInData->id) {
        // may happen after the app loses and regains active status.
        kdLogFormatMessage("Attempt to get sample count from a stale AL capture device");
        return 0;
    }
    ALCint res;
    kdThreadMutexLock(cachedInData->mutex);
    res = cachedInData->bufFilledBytes / cachedInData->sampleSize;
    kdThreadMutexUnlock(cachedInData->mutex);
    return res;
}

ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) {    
    if (!cachedInData || (int)device != cachedInData->id) {
        // may happen after the app loses and regains active status.
        kdLogFormatMessage("Attempt to get samples from a stale AL capture device");
        return;
    }
    size_t bytesToCapture = samples * cachedInData->sampleSize;
    kdAssert(cachedInData->started);
    kdAssert(bytesToCapture <= cachedInData->bufFilledBytes);

    kdThreadMutexLock(cachedInData->mutex);
    memcpy(buffer, cachedInData->buf, bytesToCapture);
    memmove(cachedInData->buf, cachedInData->buf + bytesToCapture, cachedInData->bufFilledBytes - bytesToCapture);
    cachedInData->bufFilledBytes -= bytesToCapture;
    kdThreadMutexUnlock(cachedInData->mutex);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 iOS 上使用 OpenAL 进行声音捕捉 的相关文章

  • iOS 上的三字母国家代码

    我知道您可以在 iOS 上获取所有国家 地区的两个字母的国家 地区代码 但是有没有办法获得三个字母的国家代码 So from http en wikipedia org wiki ISO 3166 1 alpha 2 http en wik
  • 从钥匙串保存和加载 |斯威夫特[重复]

    这个问题在这里已经有答案了 如何简单地将字符串存储在钥匙串中并在需要时加载 有几种SO解决方案 主要参考Git repo 但我需要最新 Swift 上最小和最简单的解决方案 当然 我不想添加 git 框架来简单地在我的项目中存储密码 有类似
  • NSNotification 与dispatch_get_main_queue

    和 关联这个问题 https stackoverflow com questions 7905192 iphone grand central dispatch main thread我想知道关于何时使用 NSNotification 在主
  • 为什么使用 UIImageJPEGRepresentation 方法通过 writetofile 保存的 .jpeg 文件大小比 ios 中的 UIImageWriteToSavedPhotosAlbum 大

    我正在尝试拯救一个UIImage设备中 jpeg 文件的对象 我正在使用这段代码 void saveImageToDocumentsDirectory UIImage mimage withFileName NSString fileNam
  • NSLocale 货币符号,显示金额值之前或之后

    我在用StoreKit在我的应用程序中实现应用程序内购买商店 我有一个自定义设计 这意味着价格的值应该是白色的且较大的 货币符号较小 较暗并与价格值的顶部对齐 我可以使用以下命令毫无问题地获取货币符号NSLocale in SKproduc
  • 如何使用 UISlider 以及如何将滑块设置为特定值?

    我是第一次使用 UIslider 首先我想知道如果值的范围是 0 到 10 如何获取滑块位置的值 其次 我希望我的滑块设置为 5 个不同的值 如 1 2 3 4 5 slider should not set between the lab
  • iOS swift 应用程序启动时出现黑屏

    我有个问题 当我启动我的应用程序时 会看到黑屏几秒钟 然后出现启动屏幕 我的启动画面不是默认的 我使用了视图控制器 因为我的启动画面有一个动画 我搜索了一个解决方案 我得到了这个 在我的闪屏加载 iPhone 之前出现黑屏 https st
  • 当我启动项目时没有 viewcontroller.swift 文件 [重复]

    这个问题在这里已经有答案了 我尝试启动该项目并使用视图控制器 但我没有看到它 仅appdelegate和scenedelegate和contentview 下面的代码应该添加到视图控制器中 但我不知道添加到哪里 它不断给我一条错误消息 指出
  • 应用因广告标识符 (IDFA) 被拒绝

    我的申请因以下原因被拒绝 您和您的应用程序 以及与您有联系的任何第三方 签订广告服务合同 可以使用广告标识符 以及通过使用广告获得的任何信息 标识符 仅用于服务广告的目的 如果一个用户 重置广告标识符 则您同意不合并 直接或间接关联 链接或
  • 使用 NSJSONSerialization 解析 JSON

    对此进行了太多讨论 但我不知道如何解决我的问题 这是我从 WorldWeatherOnline 获取的 JSON 数据 JSON 有效 但我不知道如何解析它 这是我的代码 后面是 JSON 请帮忙 NSError errorInfo NSD
  • 设置使用 iPhone 相机拍摄的图像的类型

    如果我们使用 iPhone 相机拍摄照片 图像将默认以 JPEG 格式保存 我想以其他格式 例如 PNG 保存捕获的图像 是否可以 当我们从应用程序调用 iPhone 相机时 是否可以通过代码执行此操作 我们可以设置捕获图片后必须保存的图像
  • NSPredicate 查询不包含特定字符串

    对这个问题进行了高低查找 但找不到我的答案 我正在查询核心数据以查找不等于指定字符串的所有记录 例如 所有不等于当前会话ID的记录 我已经尝试过这些但无济于事 NSPredicate predicate NSPredicate predic
  • 从后台唤醒时应用程序会重新启动

    iOS 大师您好 我已经广泛搜索了答案 但找不到答案 我打赌对我的问题的第一个答复将是另一个类似的问题 但我找不到它 不管怎样 我的问题是我正在运行一个简单的地图应用程序 用户可以在地图上放置图钉 并在放置的图钉周围有一个自定义的圆圈覆盖
  • 如何在 Monotouch 中对 UIImageView 进行运动模糊效果?

    在 MonoTouch 中进行实时运动模糊的方法是什么 当滚动惯性图片库时 我需要在 UIImageView 上应用运动模糊效果 以强度和方向作为参数 就像在 Photoshop 中一样 我在 CocoaTouch 或 CoreAnimat
  • 如何修复C风格的for语句?

    什么是正确的修复方法C 风格的 for 语句对于下面发布的代码 目前我正在交战 C 风格的 for 语句已弃用 并将在将来删除 斯威夫特的版本 var ifaddr UnsafeMutablePointer
  • iOS UITableViewCell 配件在左侧?

    对于我的应用程序 我想要一些可以同时具有复选标记和详细信息披露按钮的单元格 也就是说 我希望它们看起来与 iOS 设置中的 Wi Fi 网络选择一模一样 左侧的复选标记 中间的内容 右侧的详细信息披露按钮 有没有正确的方法来做到这一点 或者
  • 使用 Parse.com 上传视频

    我是解析新手 正在尝试保存视频并将其上传到云端 这是我正在使用的代码 但是当调用 didButtonAction 时 它不断收到错误 我相信问题出在将视频保存为文件时 但我不知道如何解决这个问题 先感谢您 void imagePickerC
  • Xcode 不会在故事板中显示我的文本字段占位符文本

    当我在属性检查器中分配文本字段的占位符值时 它不会显示在故事板中 但是 当我运行应用程序的模拟器时 它就在那里 我缺少什么设置吗 我只想能够在编辑器中看到占位符文本 下面是 xcode 和模拟器之一的屏幕截图 我遇到了同样的问题 幸运的是我
  • 编写支持 iOS 3.1.3 和 iOS 4.x 的 iOS 应用程序时的陷阱

    我想编写一个可以在 iOS 3 1 3 到 iOS 4 1 上运行的应用程序 我知道如何设置部署目标和基础 SDK 阅读 Apple 文档后 它很大程度上依赖于检查类是否可用和 或实例是否响应特定选择器 现在我的问题是 如果 Apple 从
  • 为什么我们在 @synchronized 块中传递 self ?

    我猜 synchronized 块不依赖于对象 而是依赖于线程 对吗 既然如此 我们为什么要传递 self 呢 synchronized是语言提供的用于创建同步作用域的构造 因为使用简单的全局共享互斥锁效率非常低 因此序列化每个单独的互斥锁

随机推荐