Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include "AudioUnitIO.h"
|
||||
#include "AudioInputAudioUnit.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioInputAudioUnit::AudioInputAudioUnit(std::string deviceID, AudioUnitIO* io){
|
||||
remainingDataSize=0;
|
||||
isRecording=false;
|
||||
this->io=io;
|
||||
#if TARGET_OS_OSX
|
||||
io->SetCurrentDevice(true, deviceID);
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioInputAudioUnit::~AudioInputAudioUnit(){
|
||||
|
||||
}
|
||||
|
||||
void AudioInputAudioUnit::Start(){
|
||||
isRecording=true;
|
||||
io->EnableInput(true);
|
||||
}
|
||||
|
||||
void AudioInputAudioUnit::Stop(){
|
||||
isRecording=false;
|
||||
io->EnableInput(false);
|
||||
}
|
||||
|
||||
void AudioInputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
#if TARGET_OS_OSX
|
||||
assert(remainingDataSize+buf.mDataByteSize/2<10240);
|
||||
float* src=reinterpret_cast<float*>(buf.mData);
|
||||
int16_t* dst=reinterpret_cast<int16_t*>(remainingData+remainingDataSize);
|
||||
for(int j=0;j<buf.mDataByteSize/4;j++){
|
||||
dst[j]=(int16_t)(src[j]*INT16_MAX);
|
||||
}
|
||||
remainingDataSize+=buf.mDataByteSize/2;
|
||||
#else
|
||||
assert(remainingDataSize+buf.mDataByteSize<10240);
|
||||
memcpy(remainingData+remainingDataSize, buf.mData, buf.mDataByteSize);
|
||||
remainingDataSize+=buf.mDataByteSize;
|
||||
#endif
|
||||
while(remainingDataSize>=BUFFER_SIZE*2){
|
||||
InvokeCallback((unsigned char*)remainingData, BUFFER_SIZE*2);
|
||||
remainingDataSize-=BUFFER_SIZE*2;
|
||||
if(remainingDataSize>0){
|
||||
memmove(remainingData, remainingData+(BUFFER_SIZE*2), remainingDataSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
void AudioInputAudioUnit::SetCurrentDevice(std::string deviceID){
|
||||
io->SetCurrentDevice(true, deviceID);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H
|
||||
#define LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include "../../audio/AudioInput.h"
|
||||
#include "../../utils.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioUnitIO;
|
||||
|
||||
class AudioInputAudioUnit : public AudioInput{
|
||||
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(AudioInputAudioUnit);
|
||||
AudioInputAudioUnit(std::string deviceID, AudioUnitIO* io);
|
||||
virtual ~AudioInputAudioUnit();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
#if TARGET_OS_OSX
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
#endif
|
||||
|
||||
private:
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
bool isRecording;
|
||||
AudioUnitIO* io;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTAUDIOUNIT_H
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "AudioInputAudioUnitOSX.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../audio/Resampler.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE("input: " msg": OSStatus=%d", (int)res); failed=true; return; }
|
||||
|
||||
#define kOutputBus 0
|
||||
#define kInputBus 1
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioInputAudioUnitLegacy::AudioInputAudioUnitLegacy(std::string deviceID) : AudioInput(deviceID){
|
||||
remainingDataSize=0;
|
||||
isRecording=false;
|
||||
|
||||
inBufferList.mBuffers[0].mData=malloc(10240);
|
||||
inBufferList.mBuffers[0].mDataByteSize=10240;
|
||||
inBufferList.mNumberBuffers=1;
|
||||
|
||||
OSStatus status;
|
||||
AudioComponentDescription inputDesc={
|
||||
.componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_HALOutput, .componentFlags = 0, .componentFlagsMask = 0,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple
|
||||
};
|
||||
AudioComponent component=AudioComponentFindNext(NULL, &inputDesc);
|
||||
status=AudioComponentInstanceNew(component, &unit);
|
||||
CHECK_AU_ERROR(status, "Error creating AudioUnit");
|
||||
|
||||
UInt32 flag=0;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
|
||||
flag=1;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
|
||||
CFRunLoopRef theRunLoop = NULL;
|
||||
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster };
|
||||
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
|
||||
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AURenderCallbackStruct callbackStruct;
|
||||
callbackStruct.inputProc = AudioInputAudioUnitLegacy::BufferCallback;
|
||||
callbackStruct.inputProcRefCon=this;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct));
|
||||
CHECK_AU_ERROR(status, "Error setting input buffer callback");
|
||||
status=AudioUnitInitialize(unit);
|
||||
CHECK_AU_ERROR(status, "Error initializing unit");
|
||||
}
|
||||
|
||||
AudioInputAudioUnitLegacy::~AudioInputAudioUnitLegacy(){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AudioUnitUninitialize(unit);
|
||||
AudioComponentInstanceDispose(unit);
|
||||
free(inBufferList.mBuffers[0].mData);
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::Start(){
|
||||
isRecording=true;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error starting AudioUnit");
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::Stop(){
|
||||
isRecording=false;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error stopping AudioUnit");
|
||||
}
|
||||
|
||||
OSStatus AudioInputAudioUnitLegacy::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
|
||||
AudioInputAudioUnitLegacy* input=(AudioInputAudioUnitLegacy*) inRefCon;
|
||||
input->inBufferList.mBuffers[0].mDataByteSize=10240;
|
||||
AudioUnitRender(input->unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &input->inBufferList);
|
||||
input->HandleBufferCallback(&input->inBufferList);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
size_t len=buf.mDataByteSize;
|
||||
if(hardwareSampleRate!=48000){
|
||||
len=tgvoip::audio::Resampler::Convert((int16_t*)buf.mData, (int16_t*)(remainingData+remainingDataSize), buf.mDataByteSize/2, (10240-(buf.mDataByteSize+remainingDataSize))/2, 48000, hardwareSampleRate)*2;
|
||||
}else{
|
||||
assert(remainingDataSize+buf.mDataByteSize<10240);
|
||||
memcpy(remainingData+remainingDataSize, buf.mData, buf.mDataByteSize);
|
||||
}
|
||||
remainingDataSize+=len;
|
||||
while(remainingDataSize>=BUFFER_SIZE*2){
|
||||
InvokeCallback((unsigned char*)remainingData, BUFFER_SIZE*2);
|
||||
remainingDataSize-=BUFFER_SIZE*2;
|
||||
if(remainingDataSize>0){
|
||||
memmove(remainingData, remainingData+(BUFFER_SIZE*2), remainingDataSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioInputAudioUnitLegacy::EnumerateDevices(std::vector<AudioInputDevice>& devs){
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
UInt32 dataSize = 0;
|
||||
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
|
||||
|
||||
AudioDeviceID *audioDevices = (AudioDeviceID*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Iterate through all the devices and determine which are input-capable
|
||||
propertyAddress.mScope = kAudioDevicePropertyScopeInput;
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query device name
|
||||
CFStringRef deviceName = NULL;
|
||||
dataSize = sizeof(deviceName);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine if the device is an input device (it is an input device if it has input channels)
|
||||
dataSize = 0;
|
||||
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
|
||||
status = AudioObjectGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioBufferList *bufferList = (AudioBufferList*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
|
||||
if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
|
||||
if(kAudioHardwareNoError != status)
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
|
||||
AudioInputDevice dev;
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceName, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.displayName=std::string(buf);
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.id=std::string(buf);
|
||||
if(dev.id.rfind("VPAUAggregateAudioDevice-0x")==0)
|
||||
continue;
|
||||
devs.push_back(dev);
|
||||
}
|
||||
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
}
|
||||
|
||||
void AudioInputAudioUnitLegacy::SetCurrentDevice(std::string deviceID){
|
||||
UInt32 size=sizeof(AudioDeviceID);
|
||||
AudioDeviceID inputDevice=0;
|
||||
OSStatus status;
|
||||
|
||||
if(deviceID=="default"){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 propsize = sizeof(AudioDeviceID);
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &inputDevice);
|
||||
CHECK_AU_ERROR(status, "Error getting default input device");
|
||||
}else{
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
UInt32 dataSize = 0;
|
||||
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
CHECK_AU_ERROR(status, "Error getting devices size");
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
AudioDeviceID audioDevices[deviceCount];
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
CHECK_AU_ERROR(status, "Error getting device list");
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
CHECK_AU_ERROR(status, "Error getting device uid");
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
if(deviceID==buf){
|
||||
LOGV("Found device for id %s", buf);
|
||||
inputDevice=audioDevices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!inputDevice){
|
||||
LOGW("Requested device not found, using default");
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status =AudioUnitSetProperty(unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global,
|
||||
kInputBus,
|
||||
&inputDevice,
|
||||
size);
|
||||
CHECK_AU_ERROR(status, "Error setting input device");
|
||||
|
||||
AudioStreamBasicDescription hardwareFormat;
|
||||
size=sizeof(hardwareFormat);
|
||||
status=AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kInputBus, &hardwareFormat, &size);
|
||||
CHECK_AU_ERROR(status, "Error getting hardware format");
|
||||
hardwareSampleRate=hardwareFormat.mSampleRate;
|
||||
|
||||
AudioStreamBasicDescription desiredFormat={
|
||||
.mSampleRate=hardwareFormat.mSampleRate, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
|
||||
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
|
||||
};
|
||||
|
||||
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &desiredFormat, sizeof(desiredFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting format");
|
||||
|
||||
LOGD("Switched capture device, new sample rate %d", hardwareSampleRate);
|
||||
|
||||
this->currentDevice=deviceID;
|
||||
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
size=4;
|
||||
UInt32 bufferFrameSize;
|
||||
status=AudioObjectGetPropertyData(inputDevice, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
|
||||
if(status==noErr){
|
||||
estimatedDelay=bufferFrameSize/48;
|
||||
LOGD("CoreAudio buffer size for output device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus AudioInputAudioUnitLegacy::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
|
||||
LOGV("System default input device changed");
|
||||
AudioInputAudioUnitLegacy* self=(AudioInputAudioUnitLegacy*)inClientData;
|
||||
if(self->currentDevice=="default"){
|
||||
self->SetCurrentDevice(self->currentDevice);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOINPUTAUDIOUNIT_OSX_H
|
||||
#define LIBTGVOIP_AUDIOINPUTAUDIOUNIT_OSX_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#include "../../audio/AudioInput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioInputAudioUnitLegacy : public AudioInput{
|
||||
|
||||
public:
|
||||
AudioInputAudioUnitLegacy(std::string deviceID);
|
||||
virtual ~AudioInputAudioUnitLegacy();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
static void EnumerateDevices(std::vector<AudioInputDevice>& devs);
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
|
||||
private:
|
||||
static OSStatus BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);
|
||||
static OSStatus DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData);
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
bool isRecording;
|
||||
AudioUnit unit;
|
||||
AudioBufferList inBufferList;
|
||||
int hardwareSampleRate;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOINPUTAUDIOUNIT_OSX_H
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include "AudioOutputAudioUnit.h"
|
||||
#include "../../logging.h"
|
||||
#include "AudioUnitIO.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputAudioUnit::AudioOutputAudioUnit(std::string deviceID, AudioUnitIO* io){
|
||||
isPlaying=false;
|
||||
remainingDataSize=0;
|
||||
this->io=io;
|
||||
#if TARGET_OS_OSX
|
||||
io->SetCurrentDevice(false, deviceID);
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioOutputAudioUnit::~AudioOutputAudioUnit(){
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnit::Start(){
|
||||
isPlaying=true;
|
||||
io->EnableOutput(true);
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnit::Stop(){
|
||||
isPlaying=false;
|
||||
io->EnableOutput(false);
|
||||
}
|
||||
|
||||
bool AudioOutputAudioUnit::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnit::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
if(!isPlaying){
|
||||
memset(buf.mData, 0, buf.mDataByteSize);
|
||||
return;
|
||||
}
|
||||
#if TARGET_OS_OSX
|
||||
unsigned int k;
|
||||
while(remainingDataSize<buf.mDataByteSize/2){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<sizeof(remainingData));
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
float* dst=reinterpret_cast<float*>(buf.mData);
|
||||
int16_t* src=reinterpret_cast<int16_t*>(remainingData);
|
||||
for(k=0;k<buf.mDataByteSize/4;k++){
|
||||
dst[k]=src[k]/(float)INT16_MAX;
|
||||
}
|
||||
remainingDataSize-=buf.mDataByteSize/2;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize/2, remainingDataSize);
|
||||
#else
|
||||
while(remainingDataSize<buf.mDataByteSize){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<sizeof(remainingData));
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
memcpy(buf.mData, remainingData, buf.mDataByteSize);
|
||||
remainingDataSize-=buf.mDataByteSize;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize, remainingDataSize);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
void AudioOutputAudioUnit::SetCurrentDevice(std::string deviceID){
|
||||
io->SetCurrentDevice(false, deviceID);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include "../../audio/AudioOutput.h"
|
||||
#include "../../utils.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioUnitIO;
|
||||
|
||||
class AudioOutputAudioUnit : public AudioOutput{
|
||||
public:
|
||||
TGVOIP_DISALLOW_COPY_AND_ASSIGN(AudioOutputAudioUnit);
|
||||
AudioOutputAudioUnit(std::string deviceID, AudioUnitIO* io);
|
||||
virtual ~AudioOutputAudioUnit();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
#if TARGET_OS_OSX
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool isPlaying;
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
AudioUnitIO* io;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_H
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include "AudioOutputAudioUnitOSX.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
|
||||
#define BUFFER_SIZE 960
|
||||
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE("output: " msg": OSStatus=%d", (int)res); return; }
|
||||
|
||||
#define kOutputBus 0
|
||||
#define kInputBus 1
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioOutputAudioUnitLegacy::AudioOutputAudioUnitLegacy(std::string deviceID){
|
||||
remainingDataSize=0;
|
||||
isPlaying=false;
|
||||
sysDevID=0;
|
||||
|
||||
OSStatus status;
|
||||
AudioComponentDescription inputDesc={
|
||||
.componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_HALOutput, .componentFlags = 0, .componentFlagsMask = 0,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple
|
||||
};
|
||||
AudioComponent component=AudioComponentFindNext(NULL, &inputDesc);
|
||||
status=AudioComponentInstanceNew(component, &unit);
|
||||
CHECK_AU_ERROR(status, "Error creating AudioUnit");
|
||||
|
||||
UInt32 flag=1;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
|
||||
flag=0;
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
|
||||
|
||||
char model[128];
|
||||
memset(model, 0, sizeof(model));
|
||||
size_t msize=sizeof(model);
|
||||
int mres=sysctlbyname("hw.model", model, &msize, NULL, 0);
|
||||
if(mres==0){
|
||||
LOGV("Mac model: %s", model);
|
||||
isMacBookPro=(strncmp("MacBookPro", model, 10)==0);
|
||||
}
|
||||
|
||||
SetCurrentDevice(deviceID);
|
||||
|
||||
CFRunLoopRef theRunLoop = NULL;
|
||||
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster };
|
||||
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
|
||||
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AudioStreamBasicDescription desiredFormat={
|
||||
.mSampleRate=/*hardwareFormat.mSampleRate*/48000, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
|
||||
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
|
||||
};
|
||||
|
||||
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &desiredFormat, sizeof(desiredFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting format");
|
||||
|
||||
AURenderCallbackStruct callbackStruct;
|
||||
callbackStruct.inputProc = AudioOutputAudioUnitLegacy::BufferCallback;
|
||||
callbackStruct.inputProcRefCon=this;
|
||||
status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
|
||||
CHECK_AU_ERROR(status, "Error setting input buffer callback");
|
||||
status=AudioUnitInitialize(unit);
|
||||
CHECK_AU_ERROR(status, "Error initializing unit");
|
||||
}
|
||||
|
||||
AudioOutputAudioUnitLegacy::~AudioOutputAudioUnitLegacy(){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
|
||||
AudioObjectPropertyAddress dataSourceProp={
|
||||
kAudioDevicePropertyDataSource,
|
||||
kAudioDevicePropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
if(isMacBookPro && sysDevID && AudioObjectHasProperty(sysDevID, &dataSourceProp)){
|
||||
AudioObjectRemovePropertyListener(sysDevID, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
}
|
||||
|
||||
AudioUnitUninitialize(unit);
|
||||
AudioComponentInstanceDispose(unit);
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::Start(){
|
||||
isPlaying=true;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error starting AudioUnit");
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::Stop(){
|
||||
isPlaying=false;
|
||||
OSStatus status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error stopping AudioUnit");
|
||||
}
|
||||
|
||||
OSStatus AudioOutputAudioUnitLegacy::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
|
||||
AudioOutputAudioUnitLegacy* input=(AudioOutputAudioUnitLegacy*) inRefCon;
|
||||
input->HandleBufferCallback(ioData);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
bool AudioOutputAudioUnitLegacy::IsPlaying(){
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::HandleBufferCallback(AudioBufferList *ioData){
|
||||
int i;
|
||||
for(i=0;i<ioData->mNumberBuffers;i++){
|
||||
AudioBuffer buf=ioData->mBuffers[i];
|
||||
if(!isPlaying){
|
||||
memset(buf.mData, 0, buf.mDataByteSize);
|
||||
return;
|
||||
}
|
||||
while(remainingDataSize<buf.mDataByteSize){
|
||||
assert(remainingDataSize+BUFFER_SIZE*2<10240);
|
||||
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
|
||||
remainingDataSize+=BUFFER_SIZE*2;
|
||||
}
|
||||
memcpy(buf.mData, remainingData, buf.mDataByteSize);
|
||||
remainingDataSize-=buf.mDataByteSize;
|
||||
memmove(remainingData, remainingData+buf.mDataByteSize, remainingDataSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioOutputAudioUnitLegacy::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
UInt32 dataSize = 0;
|
||||
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
|
||||
|
||||
AudioDeviceID *audioDevices = (AudioDeviceID*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i", status);
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Iterate through all the devices and determine which are input-capable
|
||||
propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query device name
|
||||
CFStringRef deviceName = NULL;
|
||||
dataSize = sizeof(deviceName);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine if the device is an input device (it is an input device if it has input channels)
|
||||
dataSize = 0;
|
||||
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
|
||||
status = AudioObjectGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
|
||||
if(kAudioHardwareNoError != status) {
|
||||
LOGE("AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioBufferList *bufferList = (AudioBufferList*)(malloc(dataSize));
|
||||
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
|
||||
if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
|
||||
if(kAudioHardwareNoError != status)
|
||||
LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
free(bufferList);
|
||||
bufferList = NULL;
|
||||
|
||||
AudioOutputDevice dev;
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceName, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.displayName=std::string(buf);
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
dev.id=std::string(buf);
|
||||
if(dev.id.rfind("VPAUAggregateAudioDevice-0x")==0)
|
||||
continue;
|
||||
devs.push_back(dev);
|
||||
}
|
||||
|
||||
free(audioDevices);
|
||||
audioDevices = NULL;
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::SetCurrentDevice(std::string deviceID){
|
||||
UInt32 size=sizeof(AudioDeviceID);
|
||||
AudioDeviceID outputDevice=0;
|
||||
OSStatus status;
|
||||
AudioObjectPropertyAddress dataSourceProp={
|
||||
kAudioDevicePropertyDataSource,
|
||||
kAudioDevicePropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
if(isMacBookPro && sysDevID && AudioObjectHasProperty(sysDevID, &dataSourceProp)){
|
||||
AudioObjectRemovePropertyListener(sysDevID, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
}
|
||||
|
||||
if(deviceID=="default"){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 propsize = sizeof(AudioDeviceID);
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &outputDevice);
|
||||
CHECK_AU_ERROR(status, "Error getting default input device");
|
||||
}else{
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
UInt32 dataSize = 0;
|
||||
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
CHECK_AU_ERROR(status, "Error getting devices size");
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
AudioDeviceID audioDevices[deviceCount];
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
CHECK_AU_ERROR(status, "Error getting device list");
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
CHECK_AU_ERROR(status, "Error getting device uid");
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
if(deviceID==buf){
|
||||
LOGV("Found device for id %s", buf);
|
||||
outputDevice=audioDevices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!outputDevice){
|
||||
LOGW("Requested device not found, using default");
|
||||
SetCurrentDevice("default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status =AudioUnitSetProperty(unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global,
|
||||
kOutputBus,
|
||||
&outputDevice,
|
||||
size);
|
||||
CHECK_AU_ERROR(status, "Error setting output device");
|
||||
|
||||
AudioStreamBasicDescription hardwareFormat;
|
||||
size=sizeof(hardwareFormat);
|
||||
status=AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kOutputBus, &hardwareFormat, &size);
|
||||
CHECK_AU_ERROR(status, "Error getting hardware format");
|
||||
hardwareSampleRate=hardwareFormat.mSampleRate;
|
||||
|
||||
AudioStreamBasicDescription desiredFormat={
|
||||
.mSampleRate=48000, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
|
||||
.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
|
||||
};
|
||||
|
||||
status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &desiredFormat, sizeof(desiredFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting format");
|
||||
|
||||
LOGD("Switched playback device, new sample rate %d", hardwareSampleRate);
|
||||
|
||||
this->currentDevice=deviceID;
|
||||
sysDevID=outputDevice;
|
||||
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
size=4;
|
||||
UInt32 bufferFrameSize;
|
||||
status=AudioObjectGetPropertyData(outputDevice, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
|
||||
if(status==noErr){
|
||||
estimatedDelay=bufferFrameSize/48;
|
||||
LOGD("CoreAudio buffer size for output device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
|
||||
}
|
||||
|
||||
if(isMacBookPro){
|
||||
if(AudioObjectHasProperty(outputDevice, &dataSourceProp)){
|
||||
UInt32 dataSource;
|
||||
size=4;
|
||||
AudioObjectGetPropertyData(outputDevice, &dataSourceProp, 0, NULL, &size, &dataSource);
|
||||
SetPanRight(dataSource=='ispk');
|
||||
AudioObjectAddPropertyListener(outputDevice, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
|
||||
}else{
|
||||
SetPanRight(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputAudioUnitLegacy::SetPanRight(bool panRight){
|
||||
LOGI("%sabling pan right on macbook pro", panRight ? "En" : "Dis");
|
||||
int32_t channelMap[]={panRight ? -1 : 0, 0};
|
||||
OSStatus status=AudioUnitSetProperty(unit, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Global, kOutputBus, channelMap, sizeof(channelMap));
|
||||
CHECK_AU_ERROR(status, "Error setting channel map");
|
||||
}
|
||||
|
||||
OSStatus AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
|
||||
AudioOutputAudioUnitLegacy* self=(AudioOutputAudioUnitLegacy*)inClientData;
|
||||
if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultOutputDevice){
|
||||
LOGV("System default input device changed");
|
||||
if(self->currentDevice=="default"){
|
||||
self->SetCurrentDevice(self->currentDevice);
|
||||
}
|
||||
}else if(inAddresses[0].mSelector==kAudioDevicePropertyDataSource){
|
||||
UInt32 dataSource;
|
||||
UInt32 size=4;
|
||||
AudioObjectGetPropertyData(inObjectID, inAddresses, 0, NULL, &size, &dataSource);
|
||||
self->SetPanRight(dataSource=='ispk');
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_OSX_H
|
||||
#define LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_OSX_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#include "../../audio/AudioOutput.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioOutputAudioUnitLegacy : public AudioOutput{
|
||||
|
||||
public:
|
||||
AudioOutputAudioUnitLegacy(std::string deviceID);
|
||||
virtual ~AudioOutputAudioUnitLegacy();
|
||||
virtual void Start();
|
||||
virtual void Stop();
|
||||
virtual bool IsPlaying();
|
||||
void HandleBufferCallback(AudioBufferList* ioData);
|
||||
static void EnumerateDevices(std::vector<AudioOutputDevice>& devs);
|
||||
virtual void SetCurrentDevice(std::string deviceID);
|
||||
|
||||
private:
|
||||
static OSStatus BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);
|
||||
static OSStatus DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData);
|
||||
void SetPanRight(bool panRight);
|
||||
unsigned char remainingData[10240];
|
||||
size_t remainingDataSize;
|
||||
bool isPlaying;
|
||||
AudioUnit unit;
|
||||
int hardwareSampleRate;
|
||||
bool isMacBookPro;
|
||||
AudioDeviceID sysDevID;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif //LIBTGVOIP_AUDIOOUTPUTAUDIOUNIT_OSX_H
|
||||
321
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.cpp
Normal file
321
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.cpp
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
#include <stdio.h>
|
||||
#include "AudioUnitIO.h"
|
||||
#include "AudioInputAudioUnit.h"
|
||||
#include "AudioOutputAudioUnit.h"
|
||||
#include "../../logging.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "../../VoIPServerConfig.h"
|
||||
|
||||
#define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE(msg": OSStatus=%d", (int)res); failed=true; return; }
|
||||
#define BUFFER_SIZE 960 // 20 ms
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
#define INPUT_BUFFER_SIZE 20480
|
||||
#else
|
||||
#define INPUT_BUFFER_SIZE 10240
|
||||
#endif
|
||||
|
||||
#define kOutputBus 0
|
||||
#define kInputBus 1
|
||||
|
||||
#if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API)
|
||||
extern "C" {
|
||||
OSStatus AudioDeviceDuck(AudioDeviceID inDevice,
|
||||
Float32 inDuckedLevel,
|
||||
const AudioTimeStamp* __nullable inStartTime,
|
||||
Float32 inRampDuration) __attribute__((weak_import));
|
||||
}
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::audio;
|
||||
|
||||
AudioUnitIO::AudioUnitIO(std::string inputDeviceID, std::string outputDeviceID){
|
||||
input=NULL;
|
||||
output=NULL;
|
||||
inputEnabled=false;
|
||||
outputEnabled=false;
|
||||
failed=false;
|
||||
started=false;
|
||||
inBufferList.mBuffers[0].mData=malloc(INPUT_BUFFER_SIZE);
|
||||
inBufferList.mBuffers[0].mDataByteSize=INPUT_BUFFER_SIZE;
|
||||
inBufferList.mNumberBuffers=1;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
DarwinSpecific::ConfigureAudioSession();
|
||||
#endif
|
||||
|
||||
OSStatus status;
|
||||
AudioComponentDescription desc;
|
||||
AudioComponent inputComponent;
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
inputComponent = AudioComponentFindNext(NULL, &desc);
|
||||
status = AudioComponentInstanceNew(inputComponent, &unit);
|
||||
|
||||
UInt32 flag=1;
|
||||
#if TARGET_OS_IPHONE
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
|
||||
#endif
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
flag=ServerConfig::GetSharedInstance()->GetBoolean("use_ios_vpio_agc", true) ? 1 : 0;
|
||||
#else
|
||||
flag=ServerConfig::GetSharedInstance()->GetBoolean("use_osx_vpio_agc", true) ? 1 : 0;
|
||||
#endif
|
||||
status=AudioUnitSetProperty(unit, kAUVoiceIOProperty_VoiceProcessingEnableAGC, kAudioUnitScope_Global, kInputBus, &flag, sizeof(flag));
|
||||
CHECK_AU_ERROR(status, "Error disabling AGC");
|
||||
|
||||
AudioStreamBasicDescription audioFormat;
|
||||
audioFormat.mSampleRate = 48000;
|
||||
audioFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
#if TARGET_OS_IPHONE
|
||||
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
|
||||
audioFormat.mBitsPerChannel = 16;
|
||||
audioFormat.mBytesPerPacket = 2;
|
||||
audioFormat.mBytesPerFrame = 2;
|
||||
#else // OS X
|
||||
audioFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
|
||||
audioFormat.mBitsPerChannel = 32;
|
||||
audioFormat.mBytesPerPacket = 4;
|
||||
audioFormat.mBytesPerFrame = 4;
|
||||
#endif
|
||||
audioFormat.mFramesPerPacket = 1;
|
||||
audioFormat.mChannelsPerFrame = 1;
|
||||
|
||||
status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting output format");
|
||||
status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, sizeof(audioFormat));
|
||||
CHECK_AU_ERROR(status, "Error setting input format");
|
||||
|
||||
AURenderCallbackStruct callbackStruct;
|
||||
|
||||
callbackStruct.inputProc = AudioUnitIO::BufferCallback;
|
||||
callbackStruct.inputProcRefCon = this;
|
||||
status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
|
||||
CHECK_AU_ERROR(status, "Error setting output buffer callback");
|
||||
status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct));
|
||||
CHECK_AU_ERROR(status, "Error setting input buffer callback");
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
CFRunLoopRef theRunLoop = NULL;
|
||||
AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster };
|
||||
status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
|
||||
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
input=new AudioInputAudioUnit(inputDeviceID, this);
|
||||
output=new AudioOutputAudioUnit(outputDeviceID, this);
|
||||
}
|
||||
|
||||
AudioUnitIO::~AudioUnitIO(){
|
||||
#if TARGET_OS_OSX
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
|
||||
#endif
|
||||
AudioOutputUnitStop(unit);
|
||||
AudioUnitUninitialize(unit);
|
||||
AudioComponentInstanceDispose(unit);
|
||||
free(inBufferList.mBuffers[0].mData);
|
||||
delete input;
|
||||
delete output;
|
||||
}
|
||||
|
||||
OSStatus AudioUnitIO::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
|
||||
((AudioUnitIO*)inRefCon)->BufferCallback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void AudioUnitIO::BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList *ioData){
|
||||
if(bus==kOutputBus){
|
||||
if(output && outputEnabled){
|
||||
output->HandleBufferCallback(ioData);
|
||||
}else{
|
||||
memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
|
||||
}
|
||||
}else if(bus==kInputBus){
|
||||
inBufferList.mBuffers[0].mDataByteSize=INPUT_BUFFER_SIZE;
|
||||
AudioUnitRender(unit, ioActionFlags, inTimeStamp, bus, numFrames, &inBufferList);
|
||||
if(input && inputEnabled){
|
||||
input->HandleBufferCallback(&inBufferList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioUnitIO::EnableInput(bool enabled){
|
||||
inputEnabled=enabled;
|
||||
StartIfNeeded();
|
||||
}
|
||||
|
||||
void AudioUnitIO::EnableOutput(bool enabled){
|
||||
outputEnabled=enabled;
|
||||
StartIfNeeded();
|
||||
#if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API)
|
||||
if(actualDuckingEnabled!=duckingEnabled){
|
||||
actualDuckingEnabled=duckingEnabled;
|
||||
AudioDeviceDuck(currentOutputDeviceID, duckingEnabled ? 0.177828f : 1.0f, NULL, 0.1f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioUnitIO::StartIfNeeded(){
|
||||
if(started)
|
||||
return;
|
||||
started=true;
|
||||
OSStatus status = AudioUnitInitialize(unit);
|
||||
CHECK_AU_ERROR(status, "Error initializing AudioUnit");
|
||||
status=AudioOutputUnitStart(unit);
|
||||
CHECK_AU_ERROR(status, "Error starting AudioUnit");
|
||||
}
|
||||
|
||||
AudioInput* AudioUnitIO::GetInput(){
|
||||
return input;
|
||||
}
|
||||
|
||||
AudioOutput* AudioUnitIO::GetOutput(){
|
||||
return output;
|
||||
}
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
OSStatus AudioUnitIO::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
|
||||
AudioUnitIO* self=(AudioUnitIO*)inClientData;
|
||||
if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultOutputDevice){
|
||||
LOGV("System default output device changed");
|
||||
if(self->currentOutputDevice=="default"){
|
||||
self->SetCurrentDevice(false, self->currentOutputDevice);
|
||||
}
|
||||
}else if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultInputDevice){
|
||||
LOGV("System default input device changed");
|
||||
if(self->currentInputDevice=="default"){
|
||||
self->SetCurrentDevice(true, self->currentInputDevice);
|
||||
}
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void AudioUnitIO::SetCurrentDevice(bool input, std::string deviceID){
|
||||
LOGV("Setting current %sput device: %s", input ? "in" : "out", deviceID.c_str());
|
||||
if(started){
|
||||
AudioOutputUnitStop(unit);
|
||||
AudioUnitUninitialize(unit);
|
||||
}
|
||||
UInt32 size=sizeof(AudioDeviceID);
|
||||
AudioDeviceID device=0;
|
||||
OSStatus status;
|
||||
|
||||
if(deviceID=="default"){
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = input ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
UInt32 propsize = sizeof(AudioDeviceID);
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &device);
|
||||
CHECK_AU_ERROR(status, "Error getting default device");
|
||||
}else{
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
UInt32 dataSize = 0;
|
||||
status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
|
||||
CHECK_AU_ERROR(status, "Error getting devices size");
|
||||
UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
|
||||
AudioDeviceID audioDevices[deviceCount];
|
||||
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
|
||||
CHECK_AU_ERROR(status, "Error getting device list");
|
||||
for(UInt32 i = 0; i < deviceCount; ++i) {
|
||||
// Query device UID
|
||||
CFStringRef deviceUID = NULL;
|
||||
dataSize = sizeof(deviceUID);
|
||||
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||
status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
|
||||
CHECK_AU_ERROR(status, "Error getting device uid");
|
||||
char buf[1024];
|
||||
CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
|
||||
if(deviceID==buf){
|
||||
LOGV("Found device for id %s", buf);
|
||||
device=audioDevices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!device){
|
||||
LOGW("Requested device not found, using default");
|
||||
SetCurrentDevice(input, "default");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status=AudioUnitSetProperty(unit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global,
|
||||
input ? kInputBus : kOutputBus,
|
||||
&device,
|
||||
size);
|
||||
CHECK_AU_ERROR(status, "Error setting input device");
|
||||
|
||||
if(input)
|
||||
currentInputDevice=deviceID;
|
||||
else
|
||||
currentOutputDevice=deviceID;
|
||||
|
||||
/*AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
size=4;
|
||||
UInt32 bufferFrameSize;
|
||||
status=AudioObjectGetPropertyData(device, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
|
||||
if(status==noErr){
|
||||
estimatedDelay=bufferFrameSize/48;
|
||||
LOGD("CoreAudio buffer size for device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
|
||||
}*/
|
||||
if(started){
|
||||
started=false;
|
||||
StartIfNeeded();
|
||||
}
|
||||
if(!input){
|
||||
currentOutputDeviceID=device;
|
||||
}
|
||||
LOGV("Set current %sput device done", input ? "in" : "out");
|
||||
}
|
||||
|
||||
void AudioUnitIO::SetDuckingEnabled(bool enabled){
|
||||
duckingEnabled=enabled;
|
||||
#ifndef TGVOIP_NO_OSX_PRIVATE_API
|
||||
if(outputEnabled && duckingEnabled!=actualDuckingEnabled){
|
||||
actualDuckingEnabled=enabled;
|
||||
AudioDeviceDuck(currentOutputDeviceID, enabled ? 0.177828f : 1.0f, NULL, 0.1f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
57
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.h
Normal file
57
TMessagesProj/jni/voip/libtgvoip/os/darwin/AudioUnitIO.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_AUDIOUNITIO_H
|
||||
#define LIBTGVOIP_AUDIOUNITIO_H
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include "../../threading.h"
|
||||
#include <string>
|
||||
#include "../../audio/AudioIO.h"
|
||||
|
||||
namespace tgvoip{ namespace audio{
|
||||
class AudioInputAudioUnit;
|
||||
class AudioOutputAudioUnit;
|
||||
|
||||
class AudioUnitIO : public AudioIO{
|
||||
public:
|
||||
AudioUnitIO(std::string inputDeviceID, std::string outputDeviceID);
|
||||
~AudioUnitIO();
|
||||
void EnableInput(bool enabled);
|
||||
void EnableOutput(bool enabled);
|
||||
virtual AudioInput* GetInput();
|
||||
virtual AudioOutput* GetOutput();
|
||||
#if TARGET_OS_OSX
|
||||
void SetCurrentDevice(bool input, std::string deviceID);
|
||||
void SetDuckingEnabled(bool enabled);
|
||||
#endif
|
||||
|
||||
private:
|
||||
static OSStatus BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);
|
||||
void BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList* ioData);
|
||||
void StartIfNeeded();
|
||||
#if TARGET_OS_OSX
|
||||
static OSStatus DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData);
|
||||
std::string currentInputDevice;
|
||||
std::string currentOutputDevice;
|
||||
bool duckingEnabled=true;
|
||||
#ifndef TGVOIP_NO_OSX_PRIVATE_API
|
||||
bool actualDuckingEnabled=true;
|
||||
#endif // TGVOIP_NO_OSX_PRIVATE_API
|
||||
AudioDeviceID currentOutputDeviceID;
|
||||
#endif
|
||||
AudioComponentInstance unit;
|
||||
AudioInputAudioUnit* input;
|
||||
AudioOutputAudioUnit* output;
|
||||
AudioBufferList inBufferList;
|
||||
bool inputEnabled;
|
||||
bool outputEnabled;
|
||||
bool started;
|
||||
};
|
||||
}}
|
||||
|
||||
#endif /* LIBTGVOIP_AUDIOUNITIO_H */
|
||||
32
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.h
Normal file
32
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef TGVOIP_DARWINSPECIFIC_H
|
||||
#define TGVOIP_DARWINSPECIFIC_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tgvoip {
|
||||
|
||||
struct CellularCarrierInfo;
|
||||
|
||||
class DarwinSpecific{
|
||||
public:
|
||||
enum{
|
||||
THREAD_PRIO_USER_INTERACTIVE,
|
||||
THREAD_PRIO_USER_INITIATED,
|
||||
THREAD_PRIO_UTILITY,
|
||||
THREAD_PRIO_BACKGROUND,
|
||||
THREAD_PRIO_DEFAULT
|
||||
};
|
||||
static void GetSystemName(char* buf, size_t len);
|
||||
static void SetCurrentThreadPriority(int priority);
|
||||
static CellularCarrierInfo GetCarrierInfo();
|
||||
static void ConfigureAudioSession();
|
||||
};
|
||||
}
|
||||
|
||||
#endif //TGVOIP_DARWINSPECIFIC_H
|
||||
110
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.mm
Normal file
110
TMessagesProj/jni/voip/libtgvoip/os/darwin/DarwinSpecific.mm
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include "DarwinSpecific.h"
|
||||
#include "../../VoIPController.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#if TARGET_OS_IOS
|
||||
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
|
||||
#import <CoreTelephony/CTCarrier.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#endif
|
||||
|
||||
using namespace tgvoip;
|
||||
|
||||
void DarwinSpecific::GetSystemName(char* buf, size_t len){
|
||||
NSString* v=[[NSProcessInfo processInfo] operatingSystemVersionString];
|
||||
strcpy(buf, [v UTF8String]);
|
||||
//[v getCString:buf maxLength:sizeof(buf) encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
void DarwinSpecific::SetCurrentThreadPriority(int priority){
|
||||
NSThread* thread=[NSThread currentThread];
|
||||
if([thread respondsToSelector:@selector(setQualityOfService:)]){
|
||||
NSQualityOfService qos;
|
||||
switch(priority){
|
||||
case THREAD_PRIO_USER_INTERACTIVE:
|
||||
qos=NSQualityOfServiceUserInteractive;
|
||||
break;
|
||||
case THREAD_PRIO_USER_INITIATED:
|
||||
qos=NSQualityOfServiceUserInitiated;
|
||||
break;
|
||||
case THREAD_PRIO_UTILITY:
|
||||
qos=NSQualityOfServiceUtility;
|
||||
break;
|
||||
case THREAD_PRIO_BACKGROUND:
|
||||
qos=NSQualityOfServiceBackground;
|
||||
break;
|
||||
case THREAD_PRIO_DEFAULT:
|
||||
default:
|
||||
qos=NSQualityOfServiceDefault;
|
||||
break;
|
||||
}
|
||||
[thread setQualityOfService:qos];
|
||||
}else{
|
||||
double p;
|
||||
switch(priority){
|
||||
case THREAD_PRIO_USER_INTERACTIVE:
|
||||
p=1.0;
|
||||
break;
|
||||
case THREAD_PRIO_USER_INITIATED:
|
||||
p=0.8;
|
||||
break;
|
||||
case THREAD_PRIO_UTILITY:
|
||||
p=0.4;
|
||||
break;
|
||||
case THREAD_PRIO_BACKGROUND:
|
||||
p=0.2;
|
||||
break;
|
||||
case THREAD_PRIO_DEFAULT:
|
||||
default:
|
||||
p=0.5;
|
||||
break;
|
||||
}
|
||||
[NSThread setThreadPriority:p];
|
||||
}
|
||||
}
|
||||
|
||||
CellularCarrierInfo DarwinSpecific::GetCarrierInfo(){
|
||||
CellularCarrierInfo info;
|
||||
#if TARGET_OS_IOS
|
||||
CTTelephonyNetworkInfo* netinfo=[CTTelephonyNetworkInfo new];
|
||||
CTCarrier* carrier=[netinfo subscriberCellularProvider];
|
||||
if(carrier){
|
||||
NSString* name=[carrier carrierName];
|
||||
NSString* mcc=[carrier mobileCountryCode];
|
||||
NSString* mnc=[carrier mobileNetworkCode];
|
||||
NSString* countryCode=[carrier isoCountryCode];
|
||||
if(name && mcc && mnc && countryCode){
|
||||
info.name=[name cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
info.mcc=[mcc cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
info.mnc=[mnc cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
info.countryCode=[[countryCode uppercaseString] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return info;
|
||||
}
|
||||
|
||||
void DarwinSpecific::ConfigureAudioSession(){
|
||||
#if TARGET_OS_IOS
|
||||
AVAudioSession* session=[AVAudioSession sharedInstance];
|
||||
NSError* error=nil;
|
||||
[session setPreferredSampleRate:48000.0 error:&error];
|
||||
if(error){
|
||||
LOGE("Failed to set preferred sample rate on AVAudioSession: %s", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
return;
|
||||
}
|
||||
[session setPreferredIOBufferDuration:0.020 error:&error];
|
||||
if(error){
|
||||
LOGE("Failed to set preferred IO buffer duration on AVAudioSession: %s", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
return;
|
||||
}
|
||||
LOGI("Configured AVAudioSession: sampleRate=%f, IOBufferDuration=%f", session.sampleRate, session.IOBufferDuration);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER
|
||||
#define TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER
|
||||
|
||||
#include "../../video/VideoRenderer.h"
|
||||
#include <vector>
|
||||
#include <objc/objc.h>
|
||||
#include <VideoToolbox/VideoToolbox.h>
|
||||
|
||||
#ifdef __OBJC__
|
||||
@class TGVVideoRenderer;
|
||||
#else
|
||||
typedef struct objc_object TGVVideoRenderer;
|
||||
#endif
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class SampleBufferDisplayLayerRenderer : public VideoRenderer{
|
||||
public:
|
||||
SampleBufferDisplayLayerRenderer(TGVVideoRenderer* renderer);
|
||||
virtual ~SampleBufferDisplayLayerRenderer();
|
||||
virtual void Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd) override;
|
||||
virtual void DecodeAndDisplay(Buffer frame, uint32_t pts) override;
|
||||
virtual void SetStreamEnabled(bool enabled) override;
|
||||
static int GetMaximumResolution();
|
||||
static std::vector<uint32_t> GetAvailableDecoders();
|
||||
private:
|
||||
TGVVideoRenderer* renderer;
|
||||
CMFormatDescriptionRef formatDesc=NULL;
|
||||
bool needReset=false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TGVOIP_SAMPLEBUFFERDISPLAYLAYERRENDERER */
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_IPHONE
|
||||
#include <UIKit/UIKit.h>
|
||||
#endif
|
||||
#include "SampleBufferDisplayLayerRenderer.h"
|
||||
#include "../../PrivateDefines.h"
|
||||
#include "../../logging.h"
|
||||
#include "TGVVideoRenderer.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::video;
|
||||
|
||||
SampleBufferDisplayLayerRenderer::SampleBufferDisplayLayerRenderer(TGVVideoRenderer* renderer) : renderer(renderer){
|
||||
|
||||
}
|
||||
|
||||
SampleBufferDisplayLayerRenderer::~SampleBufferDisplayLayerRenderer(){
|
||||
|
||||
}
|
||||
|
||||
void SampleBufferDisplayLayerRenderer::Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer>& csd){
|
||||
LOGI("video renderer reset: %d x %d", width, height);
|
||||
if(formatDesc){
|
||||
CFRelease(formatDesc);
|
||||
}
|
||||
if(codec==CODEC_AVC){
|
||||
if(csd.size()!=2){
|
||||
LOGE("H264 requires exactly 2 CSD buffers");
|
||||
return;
|
||||
}
|
||||
const uint8_t* params[]={*csd[0]+4, *csd[1]+4};
|
||||
size_t paramSizes[]={csd[0].Length()-4, csd[1].Length()-4};
|
||||
OSStatus status=CMVideoFormatDescriptionCreateFromH264ParameterSets(NULL, 2, params, paramSizes, 4, &formatDesc);
|
||||
if(status!=noErr){
|
||||
LOGE("CMVideoFormatDescriptionCreateFromH264ParameterSets failed: %d", status);
|
||||
return;
|
||||
}
|
||||
CGRect rect=CMVideoFormatDescriptionGetCleanAperture(formatDesc, true);
|
||||
LOGI("size from formatDesc: %f x %f", rect.size.width, rect.size.height);
|
||||
}else if(codec==CODEC_HEVC){
|
||||
if(@available(iOS 11.0, *)){
|
||||
if(csd.size()!=1){
|
||||
LOGE("HEVC requires exactly 1 CSD buffer");
|
||||
return;
|
||||
}
|
||||
int offsets[]={0, 0, 0};
|
||||
Buffer& buf=csd[0];
|
||||
int currentNAL=0;
|
||||
for(int i=0;i<buf.Length()-4;i++){
|
||||
if(buf[i]==0 && buf[i+1]==0 && buf[i+2]==0 && buf[i+3]==1){
|
||||
offsets[currentNAL]=i+4;
|
||||
currentNAL++;
|
||||
}
|
||||
}
|
||||
LOGV("CSD NAL offsets: %d %d %d", offsets[0], offsets[1], offsets[2]);
|
||||
if(offsets[0]==0 || offsets[1]==0 || offsets[2]==0){
|
||||
LOGE("Error splitting CSD buffer");
|
||||
return;
|
||||
}
|
||||
const uint8_t* params[]={*buf+offsets[0], *buf+offsets[1], *buf+offsets[2]};
|
||||
size_t paramSizes[]={(size_t)((offsets[1]-4)-offsets[0]), (size_t)((offsets[2]-4)-offsets[1]), (size_t)(buf.Length()-offsets[2])};
|
||||
OSStatus status=CMVideoFormatDescriptionCreateFromHEVCParameterSets(NULL, 3, params, paramSizes, 4, NULL, &formatDesc);
|
||||
if(status!=noErr){
|
||||
LOGE("CMVideoFormatDescriptionCreateFromHEVCParameterSets failed: %d", status);
|
||||
return;
|
||||
}
|
||||
CGRect rect=CMVideoFormatDescriptionGetCleanAperture(formatDesc, true);
|
||||
LOGI("size from formatDesc: %f x %f", rect.size.width, rect.size.height);
|
||||
}else{
|
||||
LOGE("HEVC not available on this OS");
|
||||
}
|
||||
}
|
||||
needReset=true;
|
||||
}
|
||||
|
||||
void SampleBufferDisplayLayerRenderer::DecodeAndDisplay(Buffer frame, uint32_t pts){
|
||||
CMBlockBufferRef blockBuffer;
|
||||
|
||||
OSStatus status=CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, *frame, frame.Length(), kCFAllocatorNull, NULL, 0, frame.Length(), 0, &blockBuffer);
|
||||
if(status!=noErr){
|
||||
LOGE("CMBlockBufferCreateWithMemoryBlock failed: %d", status);
|
||||
return;
|
||||
}
|
||||
uint32_t _len=(uint32_t)(frame.Length()-4);
|
||||
uint8_t lenBytes[]={(uint8_t)(_len >> 24), (uint8_t)(_len >> 16), (uint8_t)(_len >> 8), (uint8_t)_len};
|
||||
status=CMBlockBufferReplaceDataBytes(lenBytes, blockBuffer, 0, 4);
|
||||
if(status!=noErr){
|
||||
LOGE("CMBlockBufferReplaceDataBytes failed: %d", status);
|
||||
return;
|
||||
}
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
status=CMSampleBufferCreate(kCFAllocatorDefault, blockBuffer, true, NULL, NULL, formatDesc, 1, 0, NULL, 0, NULL, &sampleBuffer);
|
||||
if(status!=noErr){
|
||||
LOGE("CMSampleBufferCreate failed: %d", status);
|
||||
return;
|
||||
}
|
||||
|
||||
CFRelease(blockBuffer);
|
||||
CFArrayRef attachments=CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
|
||||
CFMutableDictionaryRef dict=(CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
|
||||
|
||||
[renderer _enqueueBuffer:sampleBuffer reset:needReset];
|
||||
needReset=false;
|
||||
CFRelease(sampleBuffer);
|
||||
}
|
||||
|
||||
void SampleBufferDisplayLayerRenderer::SetStreamEnabled(bool enabled){
|
||||
|
||||
}
|
||||
|
||||
int SampleBufferDisplayLayerRenderer::GetMaximumResolution(){
|
||||
#if TARGET_OS_IPHONE
|
||||
CGRect screenSize=[UIScreen mainScreen].nativeBounds;
|
||||
CGFloat minSize=std::min(screenSize.size.width, screenSize.size.height);
|
||||
if(minSize>720.f){
|
||||
return INIT_VIDEO_RES_1080;
|
||||
}else if(minSize>480.f){
|
||||
return INIT_VIDEO_RES_720;
|
||||
}else{
|
||||
return INIT_VIDEO_RES_480;
|
||||
}
|
||||
#else // OS X
|
||||
// TODO support OS X
|
||||
#endif
|
||||
return INIT_VIDEO_RES_1080;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> SampleBufferDisplayLayerRenderer::GetAvailableDecoders(){
|
||||
std::vector<uint32_t> res;
|
||||
res.push_back(CODEC_AVC);
|
||||
if(@available(iOS 11.0, *)){
|
||||
if(VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)){
|
||||
res.push_back(CODEC_HEVC);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern void (*TGVoipLoggingFunction)(NSString *);
|
||||
20
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.h
Normal file
20
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef TGVOIP_TGLOGWRAPPER_H
|
||||
#define TGVOIP_TGLOGWRAPPER_H
|
||||
|
||||
#if defined __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void __tgvoip_call_tglog(const char* format, ...);
|
||||
|
||||
#if defined __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif //TGVOIP_TGLOGWRAPPER_H
|
||||
13
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.m
Normal file
13
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGLogWrapper.m
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
void (*TGVoipLoggingFunction)(NSString *) = NULL;
|
||||
|
||||
void __tgvoip_call_tglog(const char* format, ...){
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
NSString *string = [[NSString alloc] initWithFormat:[[NSString alloc]initWithUTF8String:format] arguments:args];
|
||||
va_end(args);
|
||||
if (TGVoipLoggingFunction) {
|
||||
TGVoipLoggingFunction(string);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
typedef NS_ENUM(int, TGVStreamPauseReason){
|
||||
TGVStreamPauseReasonBackground,
|
||||
TGVStreamPauseReasonPoorConnection
|
||||
};
|
||||
|
||||
typedef NS_ENUM(int, TGVStreamStopReason){
|
||||
TGVStreamStopReasonUser,
|
||||
TGVStreamStopReasonPoorConnection
|
||||
};
|
||||
|
||||
@protocol TGVVideoRendererDelegate <NSObject>
|
||||
|
||||
- (void)incomingVideoRotationDidChange: (int)rotation;
|
||||
- (void)incomingVideoStreamWillStartWithFrameSize: (CGSize)size;
|
||||
- (void)incomingVideoStreamDidStopWithReason: (TGVStreamStopReason)reason;
|
||||
- (void)incomingVideoStreamDidPauseWithReason: (TGVStreamPauseReason)reason;
|
||||
- (void)incomingVideoStreamWillResume;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGVVideoRenderer : NSObject
|
||||
|
||||
- (instancetype)initWithDisplayLayer: (AVSampleBufferDisplayLayer *)layer delegate: (id<TGVVideoRendererDelegate>)delegate;
|
||||
- (tgvoip::video::VideoRenderer*)nativeVideoRenderer;
|
||||
|
||||
- (void)_enqueueBuffer: (CMSampleBufferRef)buffer reset: (BOOL)reset;
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#import "TGVVideoRenderer.h"
|
||||
#include "SampleBufferDisplayLayerRenderer.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
@implementation TGVVideoRenderer{
|
||||
AVSampleBufferDisplayLayer* layer;
|
||||
id<TGVVideoRendererDelegate> delegate;
|
||||
tgvoip::video::SampleBufferDisplayLayerRenderer* nativeRenderer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDisplayLayer:(AVSampleBufferDisplayLayer *)layer delegate:(nonnull id<TGVVideoRendererDelegate>)delegate{
|
||||
self=[super init];
|
||||
self->layer=layer;
|
||||
self->delegate=delegate;
|
||||
nativeRenderer=new tgvoip::video::SampleBufferDisplayLayerRenderer(self);
|
||||
layer.videoGravity=AVLayerVideoGravityResizeAspect;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc{
|
||||
delete nativeRenderer;
|
||||
}
|
||||
|
||||
- (tgvoip::video::VideoRenderer *)nativeVideoRenderer{
|
||||
return nativeRenderer;
|
||||
}
|
||||
|
||||
- (void)_enqueueBuffer: (CMSampleBufferRef)buffer reset: (BOOL)reset{
|
||||
if(reset){
|
||||
LOGV("Resetting layer");
|
||||
[layer flush];
|
||||
}
|
||||
LOGV("Enqueue buffer");
|
||||
[layer enqueueSampleBuffer:buffer];
|
||||
NSError* error=[layer error];
|
||||
if(error){
|
||||
LOGE("enqueueSampleBuffer failed: %s", [error.description cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
39
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.h
Normal file
39
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoSource;
|
||||
}
|
||||
}
|
||||
|
||||
typedef NS_ENUM(int, TGVVideoResolution){
|
||||
TGVVideoResolution1080,
|
||||
TGVVideoResolution720,
|
||||
TGVVideoResolution480,
|
||||
TGVVideoResolution360
|
||||
};
|
||||
|
||||
@protocol TGVVideoSourceDelegate <NSObject>
|
||||
|
||||
- (void)setFrameRate: (unsigned int)frameRate;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGVVideoSource : NSObject
|
||||
|
||||
- (instancetype)initWithDelegate: (id<TGVVideoSourceDelegate>)delegate;
|
||||
- (void)sendVideoFrame: (CMSampleBufferRef)buffer;
|
||||
- (TGVVideoResolution)maximumSupportedVideoResolution;
|
||||
- (void)setVideoRotation: (int)rotation;
|
||||
- (void)pauseStream;
|
||||
- (void)resumeStream;
|
||||
- (tgvoip::video::VideoSource*)nativeVideoSource;
|
||||
|
||||
@end
|
||||
51
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.mm
Normal file
51
TMessagesProj/jni/voip/libtgvoip/os/darwin/TGVVideoSource.mm
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#import "TGVVideoSource.h"
|
||||
#include "VideoToolboxEncoderSource.h"
|
||||
|
||||
@implementation TGVVideoSource{
|
||||
tgvoip::video::VideoToolboxEncoderSource* nativeSource;
|
||||
id<TGVVideoSourceDelegate> delegate;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithDelegate: (id<TGVVideoSourceDelegate>)delegate{
|
||||
self=[super init];
|
||||
nativeSource=new tgvoip::video::VideoToolboxEncoderSource();
|
||||
self->delegate=delegate;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc{
|
||||
delete nativeSource;
|
||||
}
|
||||
|
||||
- (void)sendVideoFrame: (CMSampleBufferRef)buffer{
|
||||
nativeSource->EncodeFrame(buffer);
|
||||
}
|
||||
|
||||
- (TGVVideoResolution)maximumSupportedVideoResolution{
|
||||
return TGVVideoResolution1080;
|
||||
}
|
||||
|
||||
- (void)setVideoRotation: (int)rotation{
|
||||
|
||||
}
|
||||
|
||||
- (void)pauseStream{
|
||||
|
||||
}
|
||||
|
||||
- (void)resumeStream{
|
||||
|
||||
}
|
||||
|
||||
- (tgvoip::video::VideoSource*)nativeVideoSource{
|
||||
return nativeSource;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#ifndef LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE
|
||||
#define LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE
|
||||
|
||||
#include "../../video/VideoSource.h"
|
||||
#include <CoreMedia/CoreMedia.h>
|
||||
#include <VideoToolbox/VideoToolbox.h>
|
||||
#include <vector>
|
||||
|
||||
namespace tgvoip{
|
||||
namespace video{
|
||||
class VideoToolboxEncoderSource : public VideoSource{
|
||||
public:
|
||||
VideoToolboxEncoderSource();
|
||||
virtual ~VideoToolboxEncoderSource();
|
||||
virtual void Start() override;
|
||||
virtual void Stop() override;
|
||||
virtual void Reset(uint32_t codec, int maxResolution) override;
|
||||
virtual void RequestKeyFrame() override;
|
||||
virtual void SetBitrate(uint32_t bitrate) override;
|
||||
void EncodeFrame(CMSampleBufferRef frame);
|
||||
static std::vector<uint32_t> GetAvailableEncoders();
|
||||
private:
|
||||
void EncoderCallback(OSStatus status, CMSampleBufferRef buffer, VTEncodeInfoFlags flags);
|
||||
void SetEncoderBitrateAndLimit(uint32_t bitrate);
|
||||
bool needUpdateStreamParams=true;
|
||||
uint32_t codec=0;
|
||||
VTCompressionSessionRef session=NULL;
|
||||
bool keyframeRequested=false;
|
||||
uint32_t bitrateChangeRequested=0;
|
||||
uint32_t lastBitrate=512*1024;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* LIBTGVOIP_VIDEOTOOLBOXENCODERSOURCE */
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
//
|
||||
// libtgvoip is free and unencumbered public domain software.
|
||||
// For more information, see http://unlicense.org or the UNLICENSE file
|
||||
// you should have received with this source code distribution.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include "VideoToolboxEncoderSource.h"
|
||||
#include "../../PrivateDefines.h"
|
||||
#include "../../logging.h"
|
||||
|
||||
using namespace tgvoip;
|
||||
using namespace tgvoip::video;
|
||||
|
||||
#define CHECK_ERR(err, msg) if(err!=noErr){LOGE("VideoToolboxEncoder: " msg " failed: %d", err); return;}
|
||||
|
||||
VideoToolboxEncoderSource::VideoToolboxEncoderSource(){
|
||||
|
||||
}
|
||||
|
||||
VideoToolboxEncoderSource::~VideoToolboxEncoderSource(){
|
||||
if(session){
|
||||
CFRelease(session);
|
||||
session=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::Start(){
|
||||
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::Stop(){
|
||||
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::Reset(uint32_t codec, int maxResolution){
|
||||
if(session){
|
||||
LOGV("Releasing old compression session");
|
||||
//VTCompressionSessionCompleteFrames(session, kCMTimeInvalid);
|
||||
VTCompressionSessionInvalidate(session);
|
||||
CFRelease(session);
|
||||
session=NULL;
|
||||
LOGV("Released compression session");
|
||||
}
|
||||
CMVideoCodecType codecType;
|
||||
switch(codec){
|
||||
case CODEC_AVC:
|
||||
codecType=kCMVideoCodecType_H264;
|
||||
break;
|
||||
case CODEC_HEVC:
|
||||
codecType=kCMVideoCodecType_HEVC;
|
||||
break;
|
||||
default:
|
||||
LOGE("VideoToolboxEncoder: Unsupported codec");
|
||||
return;
|
||||
}
|
||||
needUpdateStreamParams=true;
|
||||
this->codec=codec;
|
||||
// typedef void (*VTCompressionOutputCallback)(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer);
|
||||
uint32_t width, height;
|
||||
switch(maxResolution){
|
||||
case INIT_VIDEO_RES_1080:
|
||||
width=1920;
|
||||
height=1080;
|
||||
break;
|
||||
case INIT_VIDEO_RES_720:
|
||||
width=1280;
|
||||
height=720;
|
||||
break;
|
||||
case INIT_VIDEO_RES_480:
|
||||
width=854;
|
||||
height=480;
|
||||
break;
|
||||
case INIT_VIDEO_RES_360:
|
||||
default:
|
||||
width=640;
|
||||
height=360;
|
||||
break;
|
||||
}
|
||||
OSStatus status=VTCompressionSessionCreate(NULL, width, height, codecType, NULL, NULL, NULL, [](void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer){
|
||||
reinterpret_cast<VideoToolboxEncoderSource*>(outputCallbackRefCon)->EncoderCallback(status, sampleBuffer, infoFlags);
|
||||
}, this, &session);
|
||||
if(status!=noErr){
|
||||
LOGE("VTCompressionSessionCreate failed: %d", status);
|
||||
return;
|
||||
}
|
||||
LOGD("Created VTCompressionSession");
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
|
||||
CHECK_ERR(status, "VTSessionSetProperty(AllowFrameReordering)");
|
||||
int64_t interval=15;
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, (__bridge CFTypeRef)@(interval));
|
||||
CHECK_ERR(status, "VTSessionSetProperty(MaxKeyFrameIntervalDuration)");
|
||||
SetEncoderBitrateAndLimit(lastBitrate);
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
|
||||
CHECK_ERR(status, "VTSessionSetProperty(RealTime)");
|
||||
LOGD("VTCompressionSession initialized");
|
||||
|
||||
// TODO change camera frame rate dynamically based on resolution + codec
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::RequestKeyFrame(){
|
||||
keyframeRequested=true;
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::EncodeFrame(CMSampleBufferRef frame){
|
||||
if(!session)
|
||||
return;
|
||||
CMFormatDescriptionRef format=CMSampleBufferGetFormatDescription(frame);
|
||||
CMMediaType type=CMFormatDescriptionGetMediaType(format);
|
||||
if(type!=kCMMediaType_Video){
|
||||
//LOGW("Received non-video CMSampleBuffer");
|
||||
return;
|
||||
}
|
||||
if(bitrateChangeRequested){
|
||||
LOGI("VideoToolboxEocnder: setting bitrate to %u", bitrateChangeRequested);
|
||||
SetEncoderBitrateAndLimit(bitrateChangeRequested);
|
||||
lastBitrate=bitrateChangeRequested;
|
||||
bitrateChangeRequested=0;
|
||||
}
|
||||
CFDictionaryRef frameProps=NULL;
|
||||
if(keyframeRequested){
|
||||
LOGI("VideoToolboxEncoder: requesting keyframe");
|
||||
const void* keys[]={kVTEncodeFrameOptionKey_ForceKeyFrame};
|
||||
const void* values[]={kCFBooleanTrue};
|
||||
frameProps=CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
|
||||
keyframeRequested=false;
|
||||
}
|
||||
|
||||
//CMVideoDimensions size=CMVideoFormatDescriptionGetDimensions(format);
|
||||
//LOGD("EncodeFrame %d x %d", size.width, size.height);
|
||||
CVImageBufferRef imgBuffer=CMSampleBufferGetImageBuffer(frame);
|
||||
//OSType pixFmt=CVPixelBufferGetPixelFormatType(imgBuffer);
|
||||
//LOGV("pixel format: %c%c%c%c", PRINT_FOURCC(pixFmt));
|
||||
OSStatus status=VTCompressionSessionEncodeFrame(session, imgBuffer, CMSampleBufferGetPresentationTimeStamp(frame), CMSampleBufferGetDuration(frame), frameProps, NULL, NULL);
|
||||
CHECK_ERR(status, "VTCompressionSessionEncodeFrame");
|
||||
if(frameProps)
|
||||
CFRelease(frameProps);
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::SetBitrate(uint32_t bitrate){
|
||||
bitrateChangeRequested=bitrate;
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::EncoderCallback(OSStatus status, CMSampleBufferRef buffer, VTEncodeInfoFlags flags){
|
||||
if(status!=noErr){
|
||||
LOGE("EncoderCallback error: %d", status);
|
||||
return;
|
||||
}
|
||||
if(flags & kVTEncodeInfo_FrameDropped){
|
||||
LOGW("VideoToolboxEncoder: Frame dropped");
|
||||
}
|
||||
if(!CMSampleBufferGetNumSamples(buffer)){
|
||||
LOGW("Empty CMSampleBuffer");
|
||||
return;
|
||||
}
|
||||
const uint8_t startCode[]={0, 0, 0, 1};
|
||||
if(needUpdateStreamParams){
|
||||
LOGI("VideoToolboxEncoder: Updating stream params");
|
||||
CMFormatDescriptionRef format=CMSampleBufferGetFormatDescription(buffer);
|
||||
CMVideoDimensions size=CMVideoFormatDescriptionGetDimensions(format);
|
||||
width=size.width;
|
||||
height=size.height;
|
||||
csd.clear();
|
||||
if(codec==CODEC_AVC){
|
||||
for(size_t i=0;i<2;i++){
|
||||
const uint8_t* ps=NULL;
|
||||
size_t pl=0;
|
||||
status=CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, i, &ps, &pl, NULL, NULL);
|
||||
CHECK_ERR(status, "CMVideoFormatDescriptionGetH264ParameterSetAtIndex");
|
||||
Buffer b(pl+4);
|
||||
b.CopyFrom(ps, 4, pl);
|
||||
b.CopyFrom(startCode, 0, 4);
|
||||
csd.push_back(std::move(b));
|
||||
}
|
||||
}else if(codec==CODEC_HEVC){
|
||||
LOGD("here1");
|
||||
BufferOutputStream csdBuf(1024);
|
||||
for(size_t i=0;i<3;i++){
|
||||
const uint8_t* ps=NULL;
|
||||
size_t pl=0;
|
||||
status=CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, i, &ps, &pl, NULL, NULL);
|
||||
CHECK_ERR(status, "CMVideoFormatDescriptionGetHEVCParameterSetAtIndex");
|
||||
csdBuf.WriteBytes(startCode, 4);
|
||||
csdBuf.WriteBytes(ps, pl);
|
||||
}
|
||||
csd.push_back(std::move(csdBuf));
|
||||
}
|
||||
needUpdateStreamParams=false;
|
||||
}
|
||||
CMBlockBufferRef blockBuffer=CMSampleBufferGetDataBuffer(buffer);
|
||||
size_t len=CMBlockBufferGetDataLength(blockBuffer);
|
||||
|
||||
int frameFlags=0;
|
||||
CFArrayRef attachmentsArray=CMSampleBufferGetSampleAttachmentsArray(buffer, 0);
|
||||
if(attachmentsArray && CFArrayGetCount(attachmentsArray)){
|
||||
CFBooleanRef notSync;
|
||||
CFDictionaryRef dict=(CFDictionaryRef)CFArrayGetValueAtIndex(attachmentsArray, 0);
|
||||
BOOL keyExists=CFDictionaryGetValueIfPresent(dict, kCMSampleAttachmentKey_NotSync, (const void **)¬Sync);
|
||||
if(!keyExists || !CFBooleanGetValue(notSync)){
|
||||
frameFlags |= VIDEO_FRAME_FLAG_KEYFRAME;
|
||||
}
|
||||
}else{
|
||||
frameFlags |= VIDEO_FRAME_FLAG_KEYFRAME;
|
||||
}
|
||||
|
||||
Buffer frame(len);
|
||||
CMBlockBufferCopyDataBytes(blockBuffer, 0, len, *frame);
|
||||
uint32_t offset=0;
|
||||
while(offset<len){
|
||||
uint32_t nalLen=CFSwapInt32BigToHost(*reinterpret_cast<uint32_t*>(*frame+offset));
|
||||
//LOGV("NAL length %u", nalLen);
|
||||
frame.CopyFrom(startCode, offset, 4);
|
||||
offset+=nalLen+4;
|
||||
}
|
||||
callback(std::move(frame), frameFlags);
|
||||
|
||||
//LOGV("EncoderCallback: %u bytes total", (unsigned int)len);
|
||||
}
|
||||
|
||||
void VideoToolboxEncoderSource::SetEncoderBitrateAndLimit(uint32_t bitrate){
|
||||
OSStatus status=VTSessionSetProperty(session, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(bitrate));
|
||||
CHECK_ERR(status, "VTSessionSetProperty(AverageBitRate)");
|
||||
|
||||
int64_t dataLimitValue=(int64_t)(bitrate/8);
|
||||
CFNumberRef bytesPerSecond=CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitValue);
|
||||
int64_t oneValue=1;
|
||||
CFNumberRef one=CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneValue);
|
||||
const void* numbers[]={bytesPerSecond, one};
|
||||
CFArrayRef limits=CFArrayCreate(NULL, numbers, 2, &kCFTypeArrayCallBacks);
|
||||
status=VTSessionSetProperty(session, kVTCompressionPropertyKey_DataRateLimits, limits);
|
||||
CFRelease(bytesPerSecond);
|
||||
CFRelease(one);
|
||||
CFRelease(limits);
|
||||
CHECK_ERR(status, "VTSessionSetProperty(DataRateLimits");
|
||||
}
|
||||
|
||||
std::vector<uint32_t> VideoToolboxEncoderSource::GetAvailableEncoders(){
|
||||
std::vector<uint32_t> res;
|
||||
res.push_back(CODEC_AVC);
|
||||
CFArrayRef encoders;
|
||||
OSStatus status=VTCopyVideoEncoderList(NULL, &encoders);
|
||||
for(CFIndex i=0;i<CFArrayGetCount(encoders);i++){
|
||||
CFDictionaryRef v=(CFDictionaryRef)CFArrayGetValueAtIndex(encoders, i);
|
||||
NSDictionary* encoder=(__bridge NSDictionary*)v;
|
||||
//NSString* name=(NSString*)CFDictionaryGetValue(v, kVTVideoEncoderList_EncoderName);
|
||||
uint32_t codecType=[(NSNumber*)encoder[(NSString*)kVTVideoEncoderList_CodecType] intValue];
|
||||
//LOGV("Encoders[%u]: %s, %c%c%c%c", i, [(NSString*)encoder[(NSString*)kVTVideoEncoderList_EncoderName] cStringUsingEncoding:NSUTF8StringEncoding], PRINT_FOURCC(codecType));
|
||||
if(codecType==kCMVideoCodecType_HEVC){
|
||||
res.push_back(CODEC_HEVC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CFRelease(encoders);
|
||||
return res;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue