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,76 @@
//
// 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 "AudioInputAndroid.h"
#include <stdio.h>
#include "../../logging.h"
#include "JNIUtilities.h"
#include "tgnet/FileLog.h"
extern JavaVM* sharedJVM;
using namespace tgvoip;
using namespace tgvoip::audio;
jmethodID AudioInputAndroid::initMethod=NULL;
jmethodID AudioInputAndroid::releaseMethod=NULL;
jmethodID AudioInputAndroid::startMethod=NULL;
jmethodID AudioInputAndroid::stopMethod=NULL;
jmethodID AudioInputAndroid::getEnabledEffectsMaskMethod=NULL;
jclass AudioInputAndroid::jniClass=NULL;
AudioInputAndroid::AudioInputAndroid(){
jni::DoWithJNI([this](JNIEnv* env){
jmethodID ctor=env->GetMethodID(jniClass, "<init>", "(J)V");
jobject obj=env->NewObject(jniClass, ctor, (jlong)(intptr_t)this);
DEBUG_REF("AudioInputAndroid");
javaObject=env->NewGlobalRef(obj);
env->CallVoidMethod(javaObject, initMethod, 48000, 16, 1, 960*2);
enabledEffects=(unsigned int)env->CallIntMethod(javaObject, getEnabledEffectsMaskMethod);
});
running=false;
}
AudioInputAndroid::~AudioInputAndroid(){
{
MutexGuard guard(mutex);
jni::DoWithJNI([this](JNIEnv* env){
env->CallVoidMethod(javaObject, releaseMethod);
DEBUG_DELREF("AudioInputAndroid");
env->DeleteGlobalRef(javaObject);
javaObject=NULL;
});
}
}
void AudioInputAndroid::Start(){
MutexGuard guard(mutex);
jni::DoWithJNI([this](JNIEnv* env){
failed=!env->CallBooleanMethod(javaObject, startMethod);
});
running=true;
}
void AudioInputAndroid::Stop(){
MutexGuard guard(mutex);
running=false;
jni::DoWithJNI([this](JNIEnv* env){
env->CallVoidMethod(javaObject, stopMethod);
});
}
void AudioInputAndroid::HandleCallback(JNIEnv* env, jobject buffer){
if(!running)
return;
unsigned char* buf=(unsigned char*) env->GetDirectBufferAddress(buffer);
size_t len=(size_t) env->GetDirectBufferCapacity(buffer);
InvokeCallback(buf, len);
}
unsigned int AudioInputAndroid::GetEnabledEffects(){
return enabledEffects;
}

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.
//
#ifndef LIBTGVOIP_AUDIOINPUTANDROID_H
#define LIBTGVOIP_AUDIOINPUTANDROID_H
#include <jni.h>
#include "../../audio/AudioInput.h"
#include "../../threading.h"
namespace tgvoip{ namespace audio{
class AudioInputAndroid : public AudioInput{
public:
AudioInputAndroid();
virtual ~AudioInputAndroid();
virtual void Start();
virtual void Stop();
void HandleCallback(JNIEnv* env, jobject buffer);
unsigned int GetEnabledEffects();
static jmethodID initMethod;
static jmethodID releaseMethod;
static jmethodID startMethod;
static jmethodID stopMethod;
static jmethodID getEnabledEffectsMaskMethod;
static jclass jniClass;
static constexpr unsigned int EFFECT_AEC=1;
static constexpr unsigned int EFFECT_NS=2;
private:
jobject javaObject;
bool running;
Mutex mutex;
unsigned int enabledEffects=0;
};
}}
#endif //LIBTGVOIP_AUDIOINPUTANDROID_H

View file

@ -0,0 +1,137 @@
//
// 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 "AudioInputOpenSLES.h"
#include "../../logging.h"
#include "OpenSLEngineWrapper.h"
#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); return; }
#define BUFFER_SIZE 960 // 20 ms
using namespace tgvoip;
using namespace tgvoip::audio;
unsigned int AudioInputOpenSLES::nativeBufferSize;
AudioInputOpenSLES::AudioInputOpenSLES(){
slEngine=OpenSLEngineWrapper::CreateEngine();
LOGI("Native buffer size is %u samples", nativeBufferSize);
if(nativeBufferSize<BUFFER_SIZE && BUFFER_SIZE % nativeBufferSize!=0){
LOGE("20ms is not divisible by native buffer size!!");
}else if(nativeBufferSize>BUFFER_SIZE && nativeBufferSize%BUFFER_SIZE!=0){
LOGE("native buffer size is not multiple of 20ms!!");
nativeBufferSize+=nativeBufferSize%BUFFER_SIZE;
}
if(nativeBufferSize==BUFFER_SIZE)
nativeBufferSize*=2;
LOGI("Adjusted native buffer size is %u", nativeBufferSize);
buffer=(int16_t*)calloc(BUFFER_SIZE, sizeof(int16_t));
nativeBuffer=(int16_t*)calloc((size_t) nativeBufferSize, sizeof(int16_t));
slRecorderObj=NULL;
}
AudioInputOpenSLES::~AudioInputOpenSLES(){
//Stop();
(*slBufferQueue)->Clear(slBufferQueue);
(*slRecorderObj)->Destroy(slRecorderObj);
slRecorderObj=NULL;
slRecorder=NULL;
slBufferQueue=NULL;
slEngine=NULL;
OpenSLEngineWrapper::DestroyEngine();
free(buffer);
buffer=NULL;
free(nativeBuffer);
nativeBuffer=NULL;
}
void AudioInputOpenSLES::BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
((AudioInputOpenSLES*)context)->HandleSLCallback();
}
void AudioInputOpenSLES::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){
assert(slRecorderObj==NULL);
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE,
SL_IODEVICE_AUDIOINPUT,
SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
SLDataSource audioSrc = {&loc_dev, NULL};
SLDataLocator_AndroidSimpleBufferQueue loc_bq =
{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1};
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, channels, sampleRate*1000,
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
channels==2 ? (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT) : SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN};
SLDataSink audioSnk = {&loc_bq, &format_pcm};
const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION};
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
SLresult result = (*slEngine)->CreateAudioRecorder(slEngine, &slRecorderObj, &audioSrc, &audioSnk, 2, id, req);
CHECK_SL_ERROR(result, "Error creating recorder");
SLAndroidConfigurationItf recorderConfig;
result = (*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_ANDROIDCONFIGURATION, &recorderConfig);
SLint32 streamType = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
result = (*recorderConfig)->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, &streamType, sizeof(SLint32));
result=(*slRecorderObj)->Realize(slRecorderObj, SL_BOOLEAN_FALSE);
CHECK_SL_ERROR(result, "Error realizing recorder");
result=(*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_RECORD, &slRecorder);
CHECK_SL_ERROR(result, "Error getting recorder interface");
result=(*slRecorderObj)->GetInterface(slRecorderObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &slBufferQueue);
CHECK_SL_ERROR(result, "Error getting buffer queue");
result=(*slBufferQueue)->RegisterCallback(slBufferQueue, AudioInputOpenSLES::BufferCallback, this);
CHECK_SL_ERROR(result, "Error setting buffer queue callback");
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
}
void AudioInputOpenSLES::Start(){
SLresult result=(*slRecorder)->SetRecordState(slRecorder, SL_RECORDSTATE_RECORDING);
CHECK_SL_ERROR(result, "Error starting record");
}
void AudioInputOpenSLES::Stop(){
SLresult result=(*slRecorder)->SetRecordState(slRecorder, SL_RECORDSTATE_STOPPED);
CHECK_SL_ERROR(result, "Error stopping record");
}
void AudioInputOpenSLES::HandleSLCallback(){
//SLmillisecond pMsec = 0;
//(*slRecorder)->GetPosition(slRecorder, &pMsec);
//LOGI("Callback! pos=%lu", pMsec);
//InvokeCallback((unsigned char*)buffer, BUFFER_SIZE*sizeof(int16_t));
//fwrite(nativeBuffer, 1, nativeBufferSize*2, test);
if(nativeBufferSize==BUFFER_SIZE){
//LOGV("nativeBufferSize==BUFFER_SIZE");
InvokeCallback((unsigned char *) nativeBuffer, BUFFER_SIZE*sizeof(int16_t));
}else if(nativeBufferSize<BUFFER_SIZE){
//LOGV("nativeBufferSize<BUFFER_SIZE");
if(positionInBuffer>=BUFFER_SIZE){
InvokeCallback((unsigned char *) buffer, BUFFER_SIZE*sizeof(int16_t));
positionInBuffer=0;
}
memcpy(((unsigned char*)buffer)+positionInBuffer*2, nativeBuffer, (size_t)nativeBufferSize*2);
positionInBuffer+=nativeBufferSize;
}else if(nativeBufferSize>BUFFER_SIZE){
//LOGV("nativeBufferSize>BUFFER_SIZE");
for(unsigned int offset=0;offset<nativeBufferSize;offset+=BUFFER_SIZE){
InvokeCallback(((unsigned char *) nativeBuffer)+offset*2, BUFFER_SIZE*sizeof(int16_t));
}
}
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
}

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 LIBTGVOIP_AUDIOINPUTOPENSLES_H
#define LIBTGVOIP_AUDIOINPUTOPENSLES_H
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include "../../audio/AudioInput.h"
namespace tgvoip{ namespace audio{
class AudioInputOpenSLES : public AudioInput{
public:
AudioInputOpenSLES();
virtual ~AudioInputOpenSLES();
virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels);
virtual void Start();
virtual void Stop();
static unsigned int nativeBufferSize;
private:
static void BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context);
void HandleSLCallback();
SLEngineItf slEngine;
SLObjectItf slRecorderObj;
SLRecordItf slRecorder;
SLAndroidSimpleBufferQueueItf slBufferQueue;
int16_t* buffer;
int16_t* nativeBuffer;
size_t positionInBuffer;
};
}}
#endif //LIBTGVOIP_AUDIOINPUTOPENSLES_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 "AudioOutputAndroid.h"
#include <stdio.h>
#include "../../logging.h"
#include "tgnet/FileLog.h"
extern JavaVM* sharedJVM;
using namespace tgvoip;
using namespace tgvoip::audio;
jmethodID AudioOutputAndroid::initMethod=NULL;
jmethodID AudioOutputAndroid::releaseMethod=NULL;
jmethodID AudioOutputAndroid::startMethod=NULL;
jmethodID AudioOutputAndroid::stopMethod=NULL;
jclass AudioOutputAndroid::jniClass=NULL;
AudioOutputAndroid::AudioOutputAndroid(){
JNIEnv* env=NULL;
bool didAttach=false;
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
jmethodID ctor=env->GetMethodID(jniClass, "<init>", "(J)V");
jobject obj=env->NewObject(jniClass, ctor, (jlong)(intptr_t)this);
DEBUG_REF("AudioOutputAndroid");
javaObject=env->NewGlobalRef(obj);
env->CallVoidMethod(javaObject, initMethod, 48000, 16, 1, 960*2);
if(didAttach){
sharedJVM->DetachCurrentThread();
}
running=false;
}
AudioOutputAndroid::~AudioOutputAndroid(){
JNIEnv* env=NULL;
bool didAttach=false;
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
env->CallVoidMethod(javaObject, releaseMethod);
DEBUG_DELREF("AudioOutputAndroid");
env->DeleteGlobalRef(javaObject);
javaObject=NULL;
if(didAttach){
sharedJVM->DetachCurrentThread();
}
}
void AudioOutputAndroid::Start(){
JNIEnv* env=NULL;
bool didAttach=false;
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
env->CallVoidMethod(javaObject, startMethod);
if(didAttach){
sharedJVM->DetachCurrentThread();
}
running=true;
}
void AudioOutputAndroid::Stop(){
running=false;
JNIEnv* env=NULL;
bool didAttach=false;
sharedJVM->GetEnv((void**) &env, JNI_VERSION_1_6);
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
env->CallVoidMethod(javaObject, stopMethod);
if(didAttach){
sharedJVM->DetachCurrentThread();
}
}
void AudioOutputAndroid::HandleCallback(JNIEnv* env, jbyteArray buffer){
if(!running)
return;
unsigned char* buf=(unsigned char*) env->GetByteArrayElements(buffer, NULL);
size_t len=(size_t) env->GetArrayLength(buffer);
InvokeCallback(buf, len);
env->ReleaseByteArrayElements(buffer, (jbyte *) buf, 0);
}
bool AudioOutputAndroid::IsPlaying(){
return running;
}

View file

@ -0,0 +1,37 @@
//
// 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_AUDIOOUTPUTANDROID_H
#define LIBTGVOIP_AUDIOOUTPUTANDROID_H
#include <jni.h>
#include "../../audio/AudioOutput.h"
namespace tgvoip{ namespace audio{
class AudioOutputAndroid : public AudioOutput{
public:
AudioOutputAndroid();
virtual ~AudioOutputAndroid();
virtual void Start();
virtual void Stop();
virtual bool IsPlaying() override;
void HandleCallback(JNIEnv* env, jbyteArray buffer);
static jmethodID initMethod;
static jmethodID releaseMethod;
static jmethodID startMethod;
static jmethodID stopMethod;
static jclass jniClass;
private:
jobject javaObject;
bool running;
};
}}
#endif //LIBTGVOIP_AUDIOOUTPUTANDROID_H

View file

@ -0,0 +1,171 @@
//
// 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 "AudioOutputOpenSLES.h"
#include "../../logging.h"
#include "../../VoIPController.h"
#include "OpenSLEngineWrapper.h"
#include "AudioInputAndroid.h"
#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); failed=true; return; }
#define BUFFER_SIZE 960 // 20 ms
using namespace tgvoip;
using namespace tgvoip::audio;
unsigned int AudioOutputOpenSLES::nativeBufferSize;
AudioOutputOpenSLES::AudioOutputOpenSLES(){
SLresult result;
slEngine=OpenSLEngineWrapper::CreateEngine();
const SLInterfaceID pOutputMixIDs[] = {};
const SLboolean pOutputMixRequired[] = {};
result = (*slEngine)->CreateOutputMix(slEngine, &slOutputMixObj, 0, pOutputMixIDs, pOutputMixRequired);
CHECK_SL_ERROR(result, "Error creating output mix");
result = (*slOutputMixObj)->Realize(slOutputMixObj, SL_BOOLEAN_FALSE);
CHECK_SL_ERROR(result, "Error realizing output mix");
LOGI("Native buffer size is %u samples", nativeBufferSize);
/*if(nativeBufferSize<BUFFER_SIZE && BUFFER_SIZE % nativeBufferSize!=0){
LOGE("20ms is not divisible by native buffer size!!");
nativeBufferSize=BUFFER_SIZE;
}else if(nativeBufferSize>BUFFER_SIZE && nativeBufferSize%BUFFER_SIZE!=0){
LOGE("native buffer size is not multiple of 20ms!!");
nativeBufferSize+=nativeBufferSize%BUFFER_SIZE;
}
LOGI("Adjusted native buffer size is %u", nativeBufferSize);*/
buffer=(int16_t*)calloc(BUFFER_SIZE, sizeof(int16_t));
nativeBuffer=(int16_t*)calloc((size_t) nativeBufferSize, sizeof(int16_t));
slPlayerObj=NULL;
remainingDataSize=0;
}
AudioOutputOpenSLES::~AudioOutputOpenSLES(){
if(!stopped)
Stop();
(*slBufferQueue)->Clear(slBufferQueue);
LOGV("destroy slPlayerObj");
(*slPlayerObj)->Destroy(slPlayerObj);
LOGV("destroy slOutputMixObj");
(*slOutputMixObj)->Destroy(slOutputMixObj);
OpenSLEngineWrapper::DestroyEngine();
free(buffer);
free(nativeBuffer);
}
void AudioOutputOpenSLES::SetNativeBufferSize(unsigned int size){
AudioOutputOpenSLES::nativeBufferSize=size;
}
void AudioOutputOpenSLES::BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
((AudioOutputOpenSLES*)context)->HandleSLCallback();
}
void AudioOutputOpenSLES::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){
assert(slPlayerObj==NULL);
SLDataLocator_AndroidSimpleBufferQueue locatorBufferQueue =
{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1};
SLDataFormat_PCM formatPCM = {SL_DATAFORMAT_PCM, channels, sampleRate*1000,
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
channels==2 ? (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT) : SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN};
SLDataSource audioSrc = {&locatorBufferQueue, &formatPCM};
SLDataLocator_OutputMix locatorOutMix = {SL_DATALOCATOR_OUTPUTMIX, slOutputMixObj};
SLDataSink audioSnk = {&locatorOutMix, NULL};
const SLInterfaceID id[2] = {SL_IID_BUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION};
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
SLresult result = (*slEngine)->CreateAudioPlayer(slEngine, &slPlayerObj, &audioSrc, &audioSnk, 2, id, req);
CHECK_SL_ERROR(result, "Error creating player");
SLAndroidConfigurationItf playerConfig;
result = (*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_ANDROIDCONFIGURATION, &playerConfig);
SLint32 streamType = SL_ANDROID_STREAM_VOICE;
result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32));
result=(*slPlayerObj)->Realize(slPlayerObj, SL_BOOLEAN_FALSE);
CHECK_SL_ERROR(result, "Error realizing player");
result=(*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_PLAY, &slPlayer);
CHECK_SL_ERROR(result, "Error getting player interface");
result=(*slPlayerObj)->GetInterface(slPlayerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &slBufferQueue);
CHECK_SL_ERROR(result, "Error getting buffer queue");
result=(*slBufferQueue)->RegisterCallback(slBufferQueue, AudioOutputOpenSLES::BufferCallback, this);
CHECK_SL_ERROR(result, "Error setting buffer queue callback");
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
}
bool AudioOutputOpenSLES::IsPhone(){
return false;
}
void AudioOutputOpenSLES::EnableLoudspeaker(bool enabled){
}
void AudioOutputOpenSLES::Start(){
stopped=false;
SLresult result=(*slPlayer)->SetPlayState(slPlayer, SL_PLAYSTATE_PLAYING);
CHECK_SL_ERROR(result, "Error starting player");
}
void AudioOutputOpenSLES::Stop(){
stopped=true;
LOGV("Stopping OpenSL output");
SLresult result=(*slPlayer)->SetPlayState(slPlayer, SL_PLAYSTATE_PAUSED);
CHECK_SL_ERROR(result, "Error starting player");
}
void AudioOutputOpenSLES::HandleSLCallback(){
/*if(stopped){
//LOGV("left HandleSLCallback early");
return;
}*/
//LOGV("before InvokeCallback");
if(!stopped){
while(remainingDataSize<nativeBufferSize*2){
assert(remainingDataSize+BUFFER_SIZE*2<10240);
InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
remainingDataSize+=BUFFER_SIZE*2;
}
memcpy(nativeBuffer, remainingData, nativeBufferSize*2);
remainingDataSize-=nativeBufferSize*2;
if(remainingDataSize>0)
memmove(remainingData, remainingData+nativeBufferSize*2, remainingDataSize);
//InvokeCallback((unsigned char *) nativeBuffer, nativeBufferSize*sizeof(int16_t));
}else{
memset(nativeBuffer, 0, nativeBufferSize*2);
}
(*slBufferQueue)->Enqueue(slBufferQueue, nativeBuffer, nativeBufferSize*sizeof(int16_t));
//LOGV("left HandleSLCallback");
}
bool AudioOutputOpenSLES::IsPlaying(){
if(slPlayer){
uint32_t state;
(*slPlayer)->GetPlayState(slPlayer, &state);
return state==SL_PLAYSTATE_PLAYING;
}
return false;
}
float AudioOutputOpenSLES::GetLevel(){
return 0; // we don't use this anyway
}

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.
//
#ifndef LIBTGVOIP_AUDIOOUTPUTOPENSLES_H
#define LIBTGVOIP_AUDIOOUTPUTOPENSLES_H
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include "../../audio/AudioOutput.h"
namespace tgvoip{ namespace audio{
class AudioOutputOpenSLES : public AudioOutput{
public:
AudioOutputOpenSLES();
virtual ~AudioOutputOpenSLES();
virtual void Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels);
virtual bool IsPhone();
virtual void EnableLoudspeaker(bool enabled);
virtual void Start();
virtual void Stop();
virtual bool IsPlaying();
virtual float GetLevel();
static void SetNativeBufferSize(unsigned int size);
static unsigned int nativeBufferSize;
private:
static void BufferCallback(SLAndroidSimpleBufferQueueItf bq, void *context);
void HandleSLCallback();
SLEngineItf slEngine;
SLObjectItf slPlayerObj;
SLObjectItf slOutputMixObj;
SLPlayItf slPlayer;
SLAndroidSimpleBufferQueueItf slBufferQueue;
int16_t* buffer;
int16_t* nativeBuffer;
bool stopped;
unsigned char remainingData[10240];
size_t remainingDataSize;
};
}}
#endif //LIBTGVOIP_AUDIOOUTPUTANDROID_H

View file

@ -0,0 +1,70 @@
//
// Created by Grishka on 15.08.2018.
//
#ifndef LIBTGVOIP_JNIUTILITIES_H
#define LIBTGVOIP_JNIUTILITIES_H
#include <functional>
#include <jni.h>
#include <stdarg.h>
#include <string>
#include "../../Buffers.h"
extern JavaVM* sharedJVM;
namespace tgvoip{
namespace jni{
inline JNIEnv *GetEnv() {
JNIEnv *env = nullptr;
sharedJVM->GetEnv((void **) &env, JNI_VERSION_1_6);
return env;
}
inline void DoWithJNI(std::function<void(JNIEnv*)> f){
JNIEnv *env=GetEnv();
bool didAttach=false;
if(!env){
sharedJVM->AttachCurrentThread(&env, NULL);
didAttach=true;
}
f(env);
if(didAttach){
sharedJVM->DetachCurrentThread();
}
}
inline void AttachAndCallVoidMethod(jmethodID method, jobject obj, ...){
if(!method || !obj)
return;
va_list va;
va_start(va, obj);
DoWithJNI([&va, method, obj](JNIEnv* env){
env->CallVoidMethodV(obj, method, va);
});
va_end(va);
}
inline std::string JavaStringToStdString(JNIEnv* env, jstring jstr){
if(!jstr)
return "";
const char* jchars=env->GetStringUTFChars(jstr, NULL);
std::string str(jchars);
env->ReleaseStringUTFChars(jstr, jchars);
return str;
}
inline jbyteArray BufferToByteArray(JNIEnv* env, Buffer& buf){
jbyteArray arr=env->NewByteArray((jsize)buf.Length());
jbyte* elements=env->GetByteArrayElements(arr, NULL);
memcpy(elements, *buf, buf.Length());
env->ReleaseByteArrayElements(arr, elements, 0);
return arr;
}
}
}
#endif //LIBTGVOIP_JNIUTILITIES_H

View file

@ -0,0 +1,48 @@
//
// 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 <cstddef>
#include "OpenSLEngineWrapper.h"
#include "../../logging.h"
#define CHECK_SL_ERROR(res, msg) if(res!=SL_RESULT_SUCCESS){ LOGE(msg); return NULL; }
using namespace tgvoip;
using namespace tgvoip::audio;
SLObjectItf OpenSLEngineWrapper::sharedEngineObj=NULL;
SLEngineItf OpenSLEngineWrapper::sharedEngine=NULL;
int OpenSLEngineWrapper::count=0;
void OpenSLEngineWrapper::DestroyEngine(){
count--;
LOGI("release: engine instance count %d", count);
if(count==0){
(*sharedEngineObj)->Destroy(sharedEngineObj);
sharedEngineObj=NULL;
sharedEngine=NULL;
}
LOGI("after release");
}
SLEngineItf OpenSLEngineWrapper::CreateEngine(){
count++;
if(sharedEngine)
return sharedEngine;
const SLInterfaceID pIDs[1] = {SL_IID_ENGINE};
const SLboolean pIDsRequired[1] = {SL_BOOLEAN_TRUE};
SLresult result = slCreateEngine(&sharedEngineObj, 0, NULL, 1, pIDs, pIDsRequired);
CHECK_SL_ERROR(result, "Error creating engine");
result=(*sharedEngineObj)->Realize(sharedEngineObj, SL_BOOLEAN_FALSE);
CHECK_SL_ERROR(result, "Error realizing engine");
result = (*sharedEngineObj)->GetInterface(sharedEngineObj, SL_IID_ENGINE, &sharedEngine);
CHECK_SL_ERROR(result, "Error getting engine interface");
return sharedEngine;
}

View file

@ -0,0 +1,26 @@
//
// 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_OPENSLENGINEWRAPPER_H
#define LIBTGVOIP_OPENSLENGINEWRAPPER_H
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
namespace tgvoip{ namespace audio{
class OpenSLEngineWrapper{
public:
static SLEngineItf CreateEngine();
static void DestroyEngine();
private:
static SLObjectItf sharedEngineObj;
static SLEngineItf sharedEngine;
static int count;
};
}}
#endif //LIBTGVOIP_OPENSLENGINEWRAPPER_H

View file

@ -0,0 +1,150 @@
//
// Created by Grishka on 12.08.2018.
//
#include "VideoRendererAndroid.h"
#include "JNIUtilities.h"
#include "../../PrivateDefines.h"
#include "../../logging.h"
using namespace tgvoip;
using namespace tgvoip::video;
jmethodID VideoRendererAndroid::resetMethod=NULL;
jmethodID VideoRendererAndroid::decodeAndDisplayMethod=NULL;
jmethodID VideoRendererAndroid::setStreamEnabledMethod=NULL;
jmethodID VideoRendererAndroid::setRotationMethod=NULL;
std::vector<uint32_t> VideoRendererAndroid::availableDecoders;
int VideoRendererAndroid::maxResolution;
extern JavaVM* sharedJVM;
VideoRendererAndroid::VideoRendererAndroid(jobject jobj) : queue(50){
this->jobj=jobj;
}
VideoRendererAndroid::~VideoRendererAndroid(){
running=false;
Request req{
Buffer(0),
Request::Type::Shutdown
};
queue.Put(std::move(req));
thread->Join();
delete thread;
/*decoderThread.Post([this]{
decoderEnv->DeleteGlobalRef(jobj);
});*/
}
void VideoRendererAndroid::Reset(uint32_t codec, unsigned int width, unsigned int height, std::vector<Buffer> &_csd){
csd.clear();
for(Buffer& b:_csd){
csd.push_back(Buffer::CopyOf(b));
}
this->codec=codec;
this->width=width;
this->height=height;
Request req{
Buffer(0),
Request::Type::ResetDecoder
};
queue.Put(std::move(req));
Request req2{
Buffer(0),
Request::Type::UpdateStreamState
};
queue.Put(std::move(req2));
if(!thread){
thread=new Thread(std::bind(&VideoRendererAndroid::RunThread, this));
thread->Start();
}
}
void VideoRendererAndroid::DecodeAndDisplay(Buffer frame, uint32_t pts){
/*decoderThread.Post(std::bind([this](Buffer frame){
}, std::move(frame)));*/
//LOGV("2 before decode %u", (unsigned int)frame.Length());
Request req{
std::move(frame),
Request::Type::DecodeFrame
};
queue.Put(std::move(req));
}
void VideoRendererAndroid::RunThread(){
JNIEnv* env;
sharedJVM->AttachCurrentThread(&env, NULL);
constexpr size_t bufferSize=200*1024;
unsigned char* buf=reinterpret_cast<unsigned char*>(malloc(bufferSize));
jobject jbuf=env->NewDirectByteBuffer(buf, bufferSize);
uint16_t lastSetRotation=0;
while(running){
//LOGV("before get from queue");
Request request=std::move(queue.GetBlocking());
//LOGV("1 before decode %u", (unsigned int)request.Length());
if(request.type==Request::Type::Shutdown){
LOGI("Shutting down video decoder thread");
break;
}else if(request.type==Request::Type::DecodeFrame){
if(request.buffer.Length()>bufferSize){
LOGE("Frame data is too long (%u, max %u)", (int) request.buffer.Length(), (int) bufferSize);
}else{
if(lastSetRotation!=rotation){
lastSetRotation=rotation;
env->CallVoidMethod(jobj, setRotationMethod, (jint)rotation);
}
memcpy(buf, *request.buffer, request.buffer.Length());
env->CallVoidMethod(jobj, decodeAndDisplayMethod, jbuf, (jint) request.buffer.Length(), 0);
}
}else if(request.type==Request::Type::ResetDecoder){
jobjectArray jcsd=NULL;
if(!csd.empty()){
jcsd=env->NewObjectArray((jsize)csd.size(), env->FindClass("[B"), NULL);
jsize i=0;
for(Buffer& b:csd){
env->SetObjectArrayElement(jcsd, i, jni::BufferToByteArray(env, b));
i++;
}
}
std::string codecStr="";
switch(codec){
case CODEC_AVC:
codecStr="video/avc";
break;
case CODEC_HEVC:
codecStr="video/hevc";
break;
case CODEC_VP8:
codecStr="video/x-vnd.on2.vp8";
break;
case CODEC_VP9:
codecStr="video/x-vnd.on2.vp9";
break;
}
env->CallVoidMethod(jobj, resetMethod, env->NewStringUTF(codecStr.c_str()), (jint)width, (jint)height, jcsd);
}else if(request.type==Request::Type::UpdateStreamState){
env->CallVoidMethod(jobj, setStreamEnabledMethod, streamEnabled);
}
}
free(buf);
sharedJVM->DetachCurrentThread();
LOGI("==== decoder thread exiting ====");
}
void VideoRendererAndroid::SetStreamEnabled(bool enabled){
LOGI("Video stream state: %d", enabled);
streamEnabled=enabled;
Request req{
Buffer(0),
Request::Type::UpdateStreamState
};
queue.Put(std::move(req));
}
void VideoRendererAndroid::SetRotation(uint16_t rotation){
this->rotation=rotation;
}

View file

@ -0,0 +1,58 @@
//
// Created by Grishka on 12.08.2018.
//
#ifndef LIBTGVOIP_VIDEORENDERERANDROID_H
#define LIBTGVOIP_VIDEORENDERERANDROID_H
#include "../../video/VideoRenderer.h"
#include "../../MessageThread.h"
#include <jni.h>
#include "../../BlockingQueue.h"
namespace tgvoip{
namespace video{
class VideoRendererAndroid : public VideoRenderer{
public:
VideoRendererAndroid(jobject jobj);
virtual ~VideoRendererAndroid();
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;
virtual void SetRotation(uint16_t rotation) override;
static jmethodID resetMethod;
static jmethodID decodeAndDisplayMethod;
static jmethodID setStreamEnabledMethod;
static jmethodID setRotationMethod;
static std::vector<uint32_t> availableDecoders;
static int maxResolution;
private:
struct Request{
enum Type{
DecodeFrame,
ResetDecoder,
UpdateStreamState,
Shutdown
};
Buffer buffer;
Type type;
};
void RunThread();
Thread* thread=NULL;
bool running=true;
BlockingQueue<Request> queue;
std::vector<Buffer> csd;
int width;
int height;
bool streamEnabled=true;
uint32_t codec;
uint16_t rotation=0;
jobject jobj;
};
}
}
#endif //LIBTGVOIP_VIDEORENDERERANDROID_H

View file

@ -0,0 +1,90 @@
//
// Created by Grishka on 12.08.2018.
//
#include "VideoSourceAndroid.h"
#include "JNIUtilities.h"
#include "../../logging.h"
#include "../../PrivateDefines.h"
#include "tgnet/FileLog.h"
using namespace tgvoip;
using namespace tgvoip::video;
extern JavaVM* sharedJVM;
std::vector<uint32_t> VideoSourceAndroid::availableEncoders;
VideoSourceAndroid::VideoSourceAndroid(jobject jobj) : javaObject(jobj){
jni::DoWithJNI([&](JNIEnv* env){
jclass cls=env->GetObjectClass(javaObject);
startMethod=env->GetMethodID(cls, "start", "()V");
stopMethod=env->GetMethodID(cls, "stop", "()V");
prepareEncoderMethod=env->GetMethodID(cls, "prepareEncoder", "(Ljava/lang/String;I)V");
requestKeyFrameMethod=env->GetMethodID(cls, "requestKeyFrame", "()V");
setBitrateMethod=env->GetMethodID(cls, "setBitrate", "(I)V");
});
}
VideoSourceAndroid::~VideoSourceAndroid(){
jni::DoWithJNI([this](JNIEnv* env){
DEBUG_DELREF("VideoSourceAndroid");
env->DeleteGlobalRef(javaObject);
});
}
void VideoSourceAndroid::Start(){
jni::DoWithJNI([this](JNIEnv* env){
env->CallVoidMethod(javaObject, startMethod);
});
}
void VideoSourceAndroid::Stop(){
jni::DoWithJNI([this](JNIEnv* env){
env->CallVoidMethod(javaObject, stopMethod);
});
}
void VideoSourceAndroid::SendFrame(Buffer frame, uint32_t flags){
callback(frame, flags, rotation);
}
void VideoSourceAndroid::SetStreamParameters(std::vector<Buffer> csd, unsigned int width, unsigned int height){
LOGD("Video stream parameters: %d x %d", width, height);
this->width=width;
this->height=height;
this->csd=std::move(csd);
}
void VideoSourceAndroid::Reset(uint32_t codec, int maxResolution){
jni::DoWithJNI([&](JNIEnv* env){
std::string codecStr="";
switch(codec){
case CODEC_AVC:
codecStr="video/avc";
break;
case CODEC_HEVC:
codecStr="video/hevc";
break;
case CODEC_VP8:
codecStr="video/x-vnd.on2.vp8";
break;
case CODEC_VP9:
codecStr="video/x-vnd.on2.vp9";
break;
}
env->CallVoidMethod(javaObject, prepareEncoderMethod, env->NewStringUTF(codecStr.c_str()), maxResolution);
});
}
void VideoSourceAndroid::RequestKeyFrame(){
jni::DoWithJNI([this](JNIEnv* env){
env->CallVoidMethod(javaObject, requestKeyFrameMethod);
});
}
void VideoSourceAndroid::SetBitrate(uint32_t bitrate){
jni::DoWithJNI([&](JNIEnv* env){
env->CallVoidMethod(javaObject, setBitrateMethod, (jint)bitrate);
});
}

View file

@ -0,0 +1,40 @@
//
// Created by Grishka on 12.08.2018.
//
#ifndef LIBTGVOIP_VIDEOSOURCEANDROID_H
#define LIBTGVOIP_VIDEOSOURCEANDROID_H
#include "../../video/VideoSource.h"
#include "../../Buffers.h"
#include <jni.h>
#include <vector>
namespace tgvoip{
namespace video{
class VideoSourceAndroid : public VideoSource{
public:
VideoSourceAndroid(jobject jobj);
virtual ~VideoSourceAndroid();
virtual void Start() override;
virtual void Stop() override;
virtual void Reset(uint32_t codec, int maxResolution) override;
void SendFrame(Buffer frame, uint32_t flags);
void SetStreamParameters(std::vector<Buffer> csd, unsigned int width, unsigned int height);
virtual void RequestKeyFrame() override;
virtual void SetBitrate(uint32_t bitrate) override;
static std::vector<uint32_t> availableEncoders;
private:
jobject javaObject;
jmethodID prepareEncoderMethod;
jmethodID startMethod;
jmethodID stopMethod;
jmethodID requestKeyFrameMethod;
jmethodID setBitrateMethod;
};
}
}
#endif //LIBTGVOIP_VIDEOSOURCEANDROID_H