Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

play incoming audio on the first/default device #28

Merged
merged 1 commit into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 248 additions & 0 deletions src/main/java/com/zoffcc/applications/trifa/AudioSelectOutBox.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
/**
* [TRIfA], Java part of Tox Reference Implementation for Android
* Copyright (C) 2020 Zoff <[email protected]>
* <p>
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
* <p>
* 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.
* <p>
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/

package com.zoffcc.applications.trifa;

import java.util.concurrent.Semaphore;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;

/*
*
* This selects the Audio Playback Device, and iterates (processes to be ready to play) incoming Audio
*
*/
public class AudioSelectOutBox
{
private static final String TAG = "trifa.AudioSelectOutBox";

static SourceDataLine sourceDataLine = null;
static AudioFormat audioformat = null;
static Mixer.Info _SelectedItem = null;

final static Semaphore semaphore_audio_out_convert = new Semaphore(1);
static int semaphore_audio_out_convert_active_threads = 0;
static int semaphore_audio_out_convert_max_active_threads = 2;
final static Semaphore semaphore_audio_device_changes = new Semaphore(1);

final static int SAMPLE_RATE_DEFAULT = 48000;
static int CHANNELS_DEFAULT = 1;
static int SAMPLE_RATE = SAMPLE_RATE_DEFAULT;
static int CHANNELS = CHANNELS_DEFAULT;
static int SAMPLE_SIZE_BIT = 16;
public static boolean init_ready = false;
public final static int n_buf_iterate_ms = 60; // fixed ms interval for audio play (call and groups)

public synchronized static void init()
{
if (init_ready == false) {
audioformat = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE_BIT, CHANNELS, true, false);
reload_device_list();
init_ready = true;
}
}

public static void reload_device_list()
{
Mixer.Info[] mixerInfo = AudioSystem.getMixerInfo();
DataLine.Info sourceDLInfo = new DataLine.Info(SourceDataLine.class, audioformat);
for (int cnt = 0; cnt < mixerInfo.length; cnt++)
{
Mixer currentMixer = AudioSystem.getMixer(mixerInfo[cnt]);
// Log.i(TAG, "" + cnt + ":" + mixerInfo[cnt].getDescription());
if (currentMixer.isLineSupported(sourceDLInfo))
{
// Log.i(TAG, "ADD:" + cnt);
if (_SelectedItem == null) {
_SelectedItem = mixerInfo[cnt];
}
}

for (Line t : currentMixer.getTargetLines())
{
Log.i(TAG, "T:mixer_line:" + t.getLineInfo());
}

for (Line t : currentMixer.getSourceLines())
{
Log.i(TAG, "S:mixer_line:" + t.getLineInfo());
}
}

for (int cnt = 0; cnt < mixerInfo.length; cnt++)
{
Mixer currentMixer = AudioSystem.getMixer(mixerInfo[cnt]);
// Log.i(TAG, "" + cnt + ":" + mixerInfo[cnt].getDescription());
if (!currentMixer.isLineSupported(sourceDLInfo))
{
// Log.i(TAG, "ADD:+++:" + cnt);
}
}
}

public static void change_audio_format(int sample_rate, int channels)
{
try
{
Log.i(TAG, "AA::OUT::change_audio_format:001:" + sample_rate + " " + channels);
Log.i(TAG, "change_audio_format:sample_rate=" + sample_rate + " SAMPLE_RATE=" + SAMPLE_RATE + " channels=" +
channels + " CHANNELS=" + CHANNELS);
}
catch (Exception e)
{
e.printStackTrace();
}

try
{
SAMPLE_RATE = sample_rate;
CHANNELS = channels;
change_device(_SelectedItem);
Log.i(TAG, "AA::OUT::change_audio_format:099");
}
catch (Exception e)
{
e.printStackTrace();
}
}

public synchronized static void change_device(Mixer.Info i)
{
try
{
semaphore_audio_device_changes.acquire();
}
catch (Exception e)
{
e.printStackTrace();
}

Log.i(TAG, "AA::OUT::change_device:001:" + i.getDescription() + " " + SAMPLE_RATE + " " + CHANNELS);

audioformat = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE_BIT, CHANNELS, true, false);

Log.i(TAG, "change_device:001");
Log.i(TAG, "select audio out:" + i.getDescription());

// Log.i(TAG, "select audio in:?:" + mixerInfo[cnt].getDescription());
Mixer currentMixer = AudioSystem.getMixer(i);

Log.i(TAG, "select audio out:" + "sel:" + i.getDescription());

if (sourceDataLine != null)
{
try
{
sourceDataLine.stop();
}
catch (Exception e2)
{
e2.printStackTrace();
}

try
{
sourceDataLine.flush();
}
catch (Exception e2)
{
e2.printStackTrace();
}

try
{
sourceDataLine.close();
Log.i(TAG, "select out out:" + "close old line");
}
catch (Exception e2)
{
e2.printStackTrace();
}

sourceDataLine = null;
}

DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioformat);
try
{
if (currentMixer.isLineSupported(dataLineInfo))
{
Log.i(TAG, "linesupported:TRUE");
}
else
{
Log.i(TAG, "linesupported:**false**");
}

if (dataLineInfo.isFormatSupported(audioformat))
{
Log.i(TAG, "linesupported:TRUE");
}
else
{
Log.i(TAG, "linesupported:**false**");
}

// sourceDataLine = (SourceDataLine) currentMixer.getLine(dataLineInfo);
sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);

if (sourceDataLine.isRunning())
{
Log.i(TAG, "isRunning:TRUE");
}
else
{
Log.i(TAG, "isRunning:**false**");
}

sourceDataLine.open(audioformat);
sourceDataLine.start();
Log.i(TAG, "getBufferSize=" + sourceDataLine.getBufferSize());

if (sourceDataLine.isRunning())
{
Log.i(TAG, "isRunning:2:TRUE");
}
else
{
Log.i(TAG, "isRunning:2:**false**");
}
}
catch (SecurityException se1)
{
se1.printStackTrace();
Log.i(TAG, "select audio out:EE3:" + se1.getMessage());
}
catch (Exception e1)
{
e1.printStackTrace();
Log.i(TAG, "select audio out:EE2:" + e1.getMessage());
}

Log.i(TAG, "change_device:099");

semaphore_audio_device_changes.release();
Log.i(TAG, "AA::OUT::change_device:099");
}
}
103 changes: 103 additions & 0 deletions src/main/kotlin/com/zoffcc/applications/trifa/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import com.zoffcc.applications.sorm.FileDB
import com.zoffcc.applications.sorm.Filetransfer
import com.zoffcc.applications.sorm.GroupMessage
import com.zoffcc.applications.sorm.Message
import com.zoffcc.applications.trifa.AudioSelectOutBox.semaphore_audio_out_convert
import com.zoffcc.applications.trifa.AudioSelectOutBox.semaphore_audio_out_convert_active_threads
import com.zoffcc.applications.trifa.AudioSelectOutBox.semaphore_audio_out_convert_max_active_threads
import com.zoffcc.applications.trifa.HelperFiletransfer.check_auto_accept_incoming_filetransfer
import com.zoffcc.applications.trifa.HelperFiletransfer.get_incoming_filetransfer_local_filename
import com.zoffcc.applications.trifa.HelperFiletransfer.move_tmp_file_to_real_file
Expand Down Expand Up @@ -116,6 +119,7 @@ class MainActivity
//
var video_buffer_1: ByteBuffer? = null
var buffer_size_in_bytes = 0
var _recBuffer: ByteBuffer? = null

@JvmField
var PREF__database_files_dir = "."
Expand Down Expand Up @@ -1010,11 +1014,110 @@ class MainActivity
@JvmStatic
fun android_toxav_callback_audio_receive_frame_cb_method(friend_number: Long, sample_count: Long, channels: Int, sampling_rate: Long)
{
if ((avstatestore.state.call_with_friend_pubkey == null)
|| (avstatestore.state.call_with_friend_pubkey != tox_friend_get_public_key(friend_number)))
{
// it's not the currently selected friend, so do not play the audio frame
return
}

if (avstatestore.state.calling_state != AVState.CALL_STATUS.CALL_CALLING)
{
// we are not in a call, ignore incoming audio frames
return
}

if ((sampling_rate.toInt() != AudioSelectOutBox.SAMPLE_RATE) ||
(channels != AudioSelectOutBox.CHANNELS) || (_recBuffer == null) ||
(sample_count.toInt() == 0))
{
Log.i(TAG, "android_toxav_callback_audio_receive_frame_cb_method:11:1");
_recBuffer = ByteBuffer.allocateDirect((10000 * 2 * channels));
set_JNI_audio_buffer2(_recBuffer)
AudioSelectOutBox.init()
AudioSelectOutBox.change_audio_format(sampling_rate.toInt(), channels)
Log.i(TAG, "android_toxav_callback_audio_receive_frame_cb_method:11:2");
}

if (sampling_rate.toInt() != AudioSelectOutBox.SAMPLE_RATE ||
channels != AudioSelectOutBox.CHANNELS)
{
Log.i(TAG, "android_toxav_callback_audio_receive_frame_cb_method:22:1:$sampling_rate".toString() + " " + AudioSelectOutBox.SAMPLE_RATE)
AudioSelectOutBox.change_audio_format(sampling_rate.toInt(), channels)
Log.i(TAG, "android_toxav_callback_audio_receive_frame_cb_method:22:2")
}

// HINT: this signals that the audio JNI buffer is not set yet
// do NOT move this further up!!
if (sample_count.toInt() == 0)
{
Log.i(TAG, "android_toxav_callback_audio_receive_frame_cb_method:EE77:"
+ friend_number + " " + sample_count + " " + channels + " " + sampling_rate)
return
}

try
{
_recBuffer!!.rewind()
val want_bytes = (sample_count * 2 * channels).toInt()
val audio_out_byte_buffer = ByteArray(want_bytes)
_recBuffer!![audio_out_byte_buffer, 0, want_bytes]

try
{
semaphore_audio_out_convert.acquire()
if (semaphore_audio_out_convert_active_threads >= semaphore_audio_out_convert_max_active_threads)
{
Log.i(TAG, "android_toxav_callback_audio_receive_frame_cb_method:too many threads running")
semaphore_audio_out_convert.release()
return
}
semaphore_audio_out_convert.release()
} catch (e: java.lang.Exception)
{
semaphore_audio_out_convert.release()
}

val t_audio_pcm_play = Thread{
try
{
semaphore_audio_out_convert.acquire()
semaphore_audio_out_convert_active_threads++
semaphore_audio_out_convert.release()
} catch (e: java.lang.Exception)
{
semaphore_audio_out_convert.release()
}
// HINT: this acutally plays incoming Audio
// HINT: this may block!!
try
{
AudioSelectOutBox.sourceDataLine.write(audio_out_byte_buffer, 0, want_bytes)
} catch (e: java.lang.Exception)
{
Log.i(TAG, "android_toxav_callback_audio_receive_frame_cb_method:sourceDataLine.write:EE:" + e.message) // e.printStackTrace();
}
try
{
semaphore_audio_out_convert.acquire()
semaphore_audio_out_convert_active_threads--
semaphore_audio_out_convert.release()
} catch (e: java.lang.Exception)
{
semaphore_audio_out_convert.release()
}
}
t_audio_pcm_play.start()
}
catch(_: Exception)
{
}
}

@JvmStatic
fun android_toxav_callback_audio_receive_frame_pts_cb_method(friend_number: Long, sample_count: Long, channels: Int, sampling_rate: Long, pts: Long)
{
android_toxav_callback_audio_receive_frame_cb_method(friend_number, sample_count, channels, sampling_rate)
}

@JvmStatic
Expand Down
Loading