Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -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

View file

@ -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

View file

@ -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;
}

View 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.
//
#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

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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

View 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

View 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 */

View 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

View 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
}

View file

@ -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 */

View file

@ -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;
}

View file

@ -0,0 +1,3 @@
#import <Foundation/Foundation.h>
extern void (*TGVoipLoggingFunction)(NSString *);

View 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

View 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);
}
}

View file

@ -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

View file

@ -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

View 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

View 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

View file

@ -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 */

View file

@ -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 **)&notSync);
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;
}