Skip to content

Commit

Permalink
Merge pull request #37 from OPENSPHERE-Inc/multitrackaudio
Browse files Browse the repository at this point in the history
0.9.14
  • Loading branch information
hanatyan128 authored Dec 1, 2024
2 parents 4616220 + d6a2935 commit ba3deca
Show file tree
Hide file tree
Showing 19 changed files with 818 additions and 328 deletions.
11 changes: 9 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ if(ENABLE_QT)
AUTORCC ON)
endif()

target_sources(${CMAKE_PROJECT_NAME} PRIVATE src/plugin-main.cpp src/plugin-ui.cpp src/plugin-audio.cpp src/utils.cpp
src/UI/output-status-dock.cpp src/UI/resources.qrc)
target_sources(
${CMAKE_PROJECT_NAME}
PRIVATE src/plugin-main.cpp
src/plugin-ui.cpp
src/plugin-audio.cpp
src/utils.cpp
src/audio/audio-capture.cpp
src/UI/output-status-dock.cpp
src/UI/resources.qrc)

set_target_properties_plugin(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${_name})
2 changes: 1 addition & 1 deletion buildspec.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"name": "osi-branch-output",
"displayName": "Branch Output Plugin",
"version": "0.9.13",
"version": "0.9.14",
"author": "OPENSPHERE Inc.",
"website": "https://opensphere.co.jp/",
"email": "[email protected]",
Expand Down
7 changes: 7 additions & 0 deletions data/locale/ca-ES.ini
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ Track6="Pista 6"
UseAuthentication="Utilitza l'autenticació"
Username="Usuari"
Password="Contrasenya"
FilterAudio="Filtrar audio"
TrackDisabled="Desactivado"
StreamingAndRecording="Transmisión y grabación"
AudioDestination="Destino"
TrackSource%1="Pista %1 Origen"
MasterTrack%1="Pista de audio maestra %1"
MultitrackAudio="Audio multipista"
7 changes: 7 additions & 0 deletions data/locale/de-DE.ini
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ Track6="Audiospur 6"
UseAuthentication="Authentifizierung verwenden"
Username="Benutzername"
Password="Passwort"
FilterAudio="Audio filtern"
TrackDisabled="Deaktiviert"
StreamingAndRecording="Streaming und Aufnahme"
AudioDestination="Ausgabeziel"
TrackSource%1="Spur %1 Quelle"
MasterTrack%1="Master Audiospur %1"
MultitrackAudio="Mehrspur-Audio"
7 changes: 7 additions & 0 deletions data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ Track6="Track 6"
UseAuthentication="Use authentication"
Username="Username"
Password="Password"
FilterAudio="Filter Audio"
TrackDisabled="Disabled"
StreamingAndRecording="Streaming and Recording"
AudioDestination="Destination"
TrackSource%1="Track %1 Source"
MasterTrack%1="Master Audio Track %1"
MultitrackAudio="Multitrack Audio"
9 changes: 8 additions & 1 deletion data/locale/fr-FR.ini
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,11 @@ Track5="Piste 5"
Track6="Piste 6"
UseAuthentication="Utiliser l'authentification"
Username="Nom d'utilisateur"
Password="Mot de passe"
Password="Mot de passe"
FilterAudio="Filtrer l'audio"
TrackDisabled="Désactivé"
StreamingAndRecording="Streaming et enregistrement"
AudioDestination="Destination"
TrackSource%1="Piste %1 Source"
MasterTrack%1="Piste audio principale %1"
MultitrackAudio="Audio multipiste"
7 changes: 7 additions & 0 deletions data/locale/ja-JP.ini
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ Track6="トラック 6"
UseAuthentication="認証を使用する"
Username="ユーザー名"
Password="パスワード"
FilterAudio="フィルター音声"
TrackDisabled="無効"
StreamingAndRecording="配信と録画"
AudioDestination="出力先"
TrackSource%1="トラック %1 ソース"
MasterTrack%1="マスター音声トラック %1"
MultitrackAudio="マルチトラック音声"
9 changes: 8 additions & 1 deletion data/locale/ko-KR.ini
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,11 @@ Track5="트럭 5"
Track6="트럭 6"
UseAuthentication="인증 기능 사용"
Username="사용자 이름"
Password="비밀번호"
Password="비밀번호"
FilterAudio="필터 음성"
TrackDisabled="무효"
StreamingAndRecording="배달 및 녹화"
AudioDestination="출력 대상"
TrackSource%1="트랙 %1 소스"
MasterTrack%1="마스터 오디오 트랙 %1"
MultitrackAudio="멀티트랙 오디오"
7 changes: 7 additions & 0 deletions data/locale/ro-RO.ini
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ Track6="Piesa 6"
UseAuthentication="Folosește autentificarea"
Username="Nume de utilizator"
Password="Parolă"
FilterAudio="Filtrați audio"
TrackDisabled="Dezactivat"
StreamingAndRecording="Streaming și înregistrare"
AudioDestination="Destinaţie"
TrackSource%1="Track %1 Sursa"
MasterTrack%1="Master Audio Track %1"
MultitrackAudio="Audio multitrack"
7 changes: 7 additions & 0 deletions data/locale/ru-RU.ini
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ Track6="Трек 6"
UseAuthentication="Использовать вход в аккаунт"
Username="Имя пользователя"
Password="Пароль"
FilterAudio="Фильтр аудио"
TrackDisabled="отключен"
StreamingAndRecording="Потоковое вещание и запись"
AudioDestination="Место назначения"
TrackSource%1="Источник трека %1"
MasterTrack%1="Основная звуковая дорожка %1"
MultitrackAudio="Многодорожечный звук"
7 changes: 7 additions & 0 deletions data/locale/uk-UA.ini
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ Track6="Доріжка 6"
UseAuthentication="Використовувати автентифікацію"
Username="Ім’я користувача"
Password="Пароль"
FilterAudio="Відфільтрований звук"
TrackDisabled="Вимкнено"
StreamingAndRecording="Потокове передавання та запис"
AudioDestination="Пункт призначення"
TrackSource%1="Трек %1 Джерело"
MasterTrack%1="Головна звукова доріжка %1"
MultitrackAudio="Багатоканальне аудіо"
7 changes: 7 additions & 0 deletions data/locale/zh-CN.ini
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ Track6="音轨 6"
UseAuthentication="使用身份认证"
Username="用户名"
Password="密码"
FilterAudio="过滤器的音频"
TrackDisabled="已禁用"
StreamingAndRecording="直播和录制"
AudioDestination="目的地"
TrackSource%1="音轨 %1 来源"
MasterTrack%1="主音频 %1"
MultitrackAudio="多音轨音频"
255 changes: 255 additions & 0 deletions src/audio/audio-capture.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/*
Branch Output Plugin
Copyright (C) 2024 OPENSPHERE Inc. [email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/

#include <obs-module.h>

#include "audio-capture.hpp"
#include "../plugin-support.h"

#define MAX_AUDIO_BUFFER_FRAMES 131071

//--- AudioCapture class ---//

AudioCapture::AudioCapture(
const char *_name, uint32_t _samplesPerSec, speaker_layout _speakers, audio_input_callback_t _captureCallback,
QObject *parent
)
: QObject(parent),
name(_name),
samplesPerSec(_samplesPerSec),
speakers(_speakers),
audio(nullptr),
audioBuffer({0}),
audioBufferFrames(0),
audioConvBuffer(nullptr),
audioConvBufferSize(0)
{
audio_output_info aoi = {0};
aoi.name = _name;
aoi.samples_per_sec = _samplesPerSec;
aoi.speakers = _speakers;
aoi.format = AUDIO_FORMAT_FLOAT_PLANAR;
aoi.input_param = this;
aoi.input_callback = _captureCallback;

if (audio_output_open(&audio, &aoi) < 0) {
audio = nullptr;
return;
}

active = true;
}

AudioCapture::~AudioCapture()
{
active = false;

if (audio) {
audio_output_close(audio);
}

deque_free(&audioBuffer);
bfree(audioConvBuffer);
}

uint64_t AudioCapture::popAudio(uint64_t startTsIn, uint32_t mixers, audio_output_data *audioData)
{
if (!active) {
return startTsIn;
}

QMutexLocker locker(&audioBufferMutex);
{
if (audioBufferFrames < AUDIO_OUTPUT_FRAMES) {
// Wait until enough frames are receved.
// DO NOT stall audio output pipeline
return startTsIn;
}

size_t maxFrames = AUDIO_OUTPUT_FRAMES;

while (maxFrames > 0 && audioBufferFrames) {
// Peek header of first chunk
deque_peek_front(&audioBuffer, audioConvBuffer, sizeof(AudioBufferHeader));
auto header = (AudioBufferHeader *)audioConvBuffer;
auto dataSize = sizeof(AudioBufferHeader) + header->speakers * header->frames * 4;

// Read chunk data
deque_peek_front(&audioBuffer, audioConvBuffer, dataSize);

auto chunkFrames = header->frames - header->offset;
auto frames = (chunkFrames > maxFrames) ? maxFrames : chunkFrames;
auto outOffset = AUDIO_OUTPUT_FRAMES - maxFrames;

for (auto tr = 0; tr < MAX_AUDIO_MIXES; tr++) {
if ((mixers & (1 << tr)) == 0) {
continue;
}
for (auto ch = 0; ch < header->speakers; ch++) {
auto out = audioData[tr].data[ch] + outOffset;
if (!header->data_idx[ch]) {
continue;
}
auto in = (float *)(audioConvBuffer + header->data_idx[ch]) + header->offset;

for (size_t i = 0; i < frames; i++) {
*out += *(in++);
if (*out > 1.0f) {
*out = 1.0f;
} else if (*out < -1.0f) {
*out = -1.0f;
}
out++;
}
}
}

if (frames == chunkFrames) {
// Remove fulfilled chunk from buffer
deque_pop_front(&audioBuffer, NULL, dataSize);
} else {
// Chunk frames are remaining -> modify chunk header
header->offset += frames;
deque_place(&audioBuffer, 0, header, sizeof(AudioBufferHeader));
}

maxFrames -= frames;

// Decrement buffer usage
audioBufferFrames -= frames;
}
}
locker.unlock();

return startTsIn;
}

void AudioCapture::pushAudio(const audio_data *audioData)
{
if (!active) {
return;
}

QMutexLocker locker(&audioBufferMutex);
{
// Push audio data to buffer
if (audioBufferFrames + audioData->frames > MAX_AUDIO_BUFFER_FRAMES) {
obs_log(LOG_WARNING, "%s: The audio buffer is full", qUtf8Printable(name));
deque_free(&audioBuffer);
deque_init(&audioBuffer);
audioBufferFrames = 0;
}

// Compute header
AudioBufferHeader header = {0};
header.frames = audioData->frames;
header.timestamp = audioData->timestamp;
header.samples_per_sec = samplesPerSec;
header.speakers = speakers;
header.format = AUDIO_FORMAT_FLOAT_PLANAR;

for (auto i = 0, channels = 0; i < header.speakers; i++) {
if (!audioData->data[i]) {
continue;
}
header.data_idx[i] = sizeof(AudioBufferHeader) + channels * audioData->frames * 4;
channels++;
}

// Push audio data to buffer
deque_push_back(&audioBuffer, &header, sizeof(AudioBufferHeader));
for (auto i = 0; i < header.speakers; i++) {
if (!audioData->data[i]) {
continue;
}
deque_push_back(&audioBuffer, audioData->data[i], audioData->frames * 4);
}

auto dataSize = sizeof(AudioBufferHeader) + header.speakers * header.frames * 4;
if (dataSize > audioConvBufferSize) {
obs_log(
LOG_DEBUG, "%s: Expand audioConvBuffer from %zu to %zu bytes", qUtf8Printable(name),
audioConvBufferSize, dataSize
);
audioConvBuffer = (uint8_t *)brealloc(audioConvBuffer, dataSize);
audioConvBufferSize = dataSize;
}

audioBufferFrames += audioData->frames;
}
locker.unlock();
}

// Convert obs_audio_data to audio_data
void AudioCapture::pushAudio(const obs_audio_data *audioData)
{
audio_data ad = {0};
ad.frames = audioData->frames;
ad.timestamp = audioData->timestamp;
memcpy(ad.data, audioData->data, sizeof(audio_data::data));

pushAudio(&ad);
}

// Callback from audio_output_open
bool AudioCapture::audioCapture(
void *param, uint64_t start_ts_in, uint64_t, uint64_t *out_ts, uint32_t mixers, audio_output_data *mixes
)
{
auto *audioSource = static_cast<AudioCapture *>(param);
*out_ts = audioSource->popAudio(start_ts_in, mixers, mixes);
return true;
}

// Callback from audio_output_open
bool AudioCapture::silenceCapture(void *, uint64_t startTsIn, uint64_t, uint64_t *outTs, uint32_t, audio_output_data *)
{
*outTs = startTsIn;
return true;
}

//--- SourceAudioCapture class ---//

SourceAudioCapture::SourceAudioCapture(
obs_source_t *source, uint32_t _samplesPerSec, speaker_layout _speakers, QObject *parent
)
: AudioCapture(obs_source_get_name(source), _samplesPerSec, _speakers, AudioCapture::audioCapture, parent),
weakSource(obs_source_get_weak_source(source))
{
obs_source_add_audio_capture_callback(source, sourceAudioCallback, this);
obs_log(LOG_DEBUG, "%s: Source audio capture created.", obs_source_get_name(source));
}

SourceAudioCapture::~SourceAudioCapture()
{
OBSSourceAutoRelease source = obs_weak_source_get_source(weakSource);
obs_source_remove_audio_capture_callback(source, sourceAudioCallback, this);

obs_log(LOG_DEBUG, "%s: Source audio capture destroyed.", obs_source_get_name(source));
}

// Callback from obs_source_add_audio_capture_callback
void SourceAudioCapture::sourceAudioCallback(void *param, obs_source_t *, const audio_data *audioData, bool muted)
{
if (muted) {
return;
}

auto sourceCapture = (SourceAudioCapture *)param;
sourceCapture->pushAudio(audioData);
}
Loading

0 comments on commit ba3deca

Please sign in to comment.