mirror of
https://github.com/moonlight-stream/moonlight-embedded.git
synced 2025-07-01 15:25:35 +00:00
Less audio stutter
This commit is contained in:
parent
ba65e6252e
commit
fe829b32fe
@ -2,29 +2,60 @@
|
||||
* in this header */
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#define CHECK_RETURN(f) if ((rc = f) != 0) return rc;
|
||||
|
||||
snd_pcm_t *handle;
|
||||
unsigned int channels;
|
||||
|
||||
int nv_alsa_init(unsigned int channelCount, unsigned int sampleRate, unsigned char* device) {
|
||||
int rc;
|
||||
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
snd_pcm_sw_params_t *sw_params;
|
||||
snd_pcm_uframes_t period_size = 240 * channelCount * 2;
|
||||
snd_pcm_uframes_t buffer_size = 12 * period_size;
|
||||
|
||||
channels = channelCount;
|
||||
|
||||
/* Open PCM device for playback. */
|
||||
if ((rc = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) != 0)
|
||||
return rc;
|
||||
|
||||
if ((rc = snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, channelCount, sampleRate, 1, 50000)) != 0) //50ms latency
|
||||
return rc;
|
||||
CHECK_RETURN(snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK))
|
||||
|
||||
/* Set hardware parameters */
|
||||
CHECK_RETURN(snd_pcm_hw_params_malloc(&hw_params));
|
||||
CHECK_RETURN(snd_pcm_hw_params_any(handle, hw_params));
|
||||
CHECK_RETURN(snd_pcm_hw_params_set_access(handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED));
|
||||
CHECK_RETURN(snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S16_LE));
|
||||
CHECK_RETURN(snd_pcm_hw_params_set_rate_near(handle, hw_params, &sampleRate, NULL));
|
||||
CHECK_RETURN(snd_pcm_hw_params_set_channels(handle, hw_params, channelCount));
|
||||
CHECK_RETURN(snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &buffer_size));
|
||||
CHECK_RETURN(snd_pcm_hw_params_set_period_size_near(handle, hw_params, &period_size, NULL));
|
||||
CHECK_RETURN(snd_pcm_hw_params(handle, hw_params));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
|
||||
/* Set software parameters */
|
||||
CHECK_RETURN(snd_pcm_sw_params_malloc(&sw_params));
|
||||
CHECK_RETURN(snd_pcm_sw_params_current(handle, sw_params));
|
||||
CHECK_RETURN(snd_pcm_sw_params_set_start_threshold(handle, sw_params, buffer_size - period_size));
|
||||
CHECK_RETURN(snd_pcm_sw_params_set_avail_min(handle, sw_params, period_size));
|
||||
CHECK_RETURN(snd_pcm_sw_params(handle, sw_params));
|
||||
snd_pcm_sw_params_free(sw_params);
|
||||
|
||||
CHECK_RETURN(snd_pcm_prepare(handle));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nv_alsa_play(const unsigned char* indata, int data_len) {
|
||||
int frames = data_len/4; /* 2 bytes/sample, 2 channels */
|
||||
int frames = data_len / (2 * channels); /* 2 bytes/sample */
|
||||
int rc = snd_pcm_writei(handle, indata, frames);
|
||||
if (rc == -EPIPE)
|
||||
snd_pcm_prepare(handle);
|
||||
snd_pcm_recover(handle, rc, 1);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int nv_alsa_close(void) {
|
||||
snd_pcm_drain(handle);
|
||||
snd_pcm_close(handle);
|
||||
if (handle != NULL) {
|
||||
snd_pcm_drain(handle);
|
||||
snd_pcm_close(handle);
|
||||
}
|
||||
}
|
||||
|
@ -6,22 +6,11 @@
|
||||
|
||||
OpusDecoder* decoder;
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma comment(lib, "opus.lib")
|
||||
#pragma comment(lib, "celt.lib")
|
||||
#pragma comment(lib, "silk_common.lib")
|
||||
#pragma comment(lib, "silk_float.lib")
|
||||
#endif
|
||||
|
||||
|
||||
// This function must be called before
|
||||
// any other decoding functions
|
||||
int nv_opus_init(void) {
|
||||
int nv_opus_init(unsigned int channelcount, unsigned int samplerate) {
|
||||
int err;
|
||||
decoder = opus_decoder_create(
|
||||
nv_opus_get_sample_rate(),
|
||||
nv_opus_get_channel_count(),
|
||||
&err);
|
||||
decoder = opus_decoder_create(samplerate, channelcount, &err);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -33,32 +22,16 @@ void nv_opus_destroy(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// The Opus stream is stereo
|
||||
int nv_opus_get_channel_count(void) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// This number assumes 2 channels with 16-bit samples at 48 KHz with 2.5 ms frames
|
||||
int nv_opus_get_max_out_shorts(void) {
|
||||
return 240*nv_opus_get_channel_count();
|
||||
}
|
||||
|
||||
// The Opus stream is 48 KHz
|
||||
int nv_opus_get_sample_rate(void) {
|
||||
return 48000;
|
||||
}
|
||||
|
||||
// outpcmdata must be 5760*2 shorts in length
|
||||
// packets must be decoded in order
|
||||
// a packet loss must call this function with NULL indata and 0 inlen
|
||||
// returns the number of decoded samples
|
||||
int nv_opus_decode(unsigned char* indata, int inlen, short* outpcmdata) {
|
||||
int nv_opus_decode(unsigned char* indata, int inlen, int framesize, short* outpcmdata) {
|
||||
int err;
|
||||
|
||||
// Decoding to 16-bit PCM with FEC off
|
||||
// Maximum length assuming 48KHz sample rate
|
||||
err = opus_decode(decoder, indata, inlen,
|
||||
outpcmdata, 512, 0);
|
||||
err = opus_decode(decoder, indata, inlen, outpcmdata, framesize, 0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
int nv_opus_init(void);
|
||||
int nv_opus_init(unsigned int channelcount, unsigned int samplerate);
|
||||
void nv_opus_destroy(void);
|
||||
int nv_opus_get_channel_count(void);
|
||||
int nv_opus_get_max_out_shorts(void);
|
||||
int nv_opus_get_sample_rate(void);
|
||||
int nv_opus_decode(unsigned char* indata, int inlen, short* outpcmdata);
|
||||
int nv_opus_decode(unsigned char* indata, int inlen, int framelen, short* outpcmdata);
|
||||
|
@ -8,8 +8,8 @@
|
||||
// This function must be called before
|
||||
// any other decoding functions
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_binding_audio_OpusDecoder_init(JNIEnv *env, jobject this) {
|
||||
return nv_opus_init();
|
||||
Java_com_limelight_binding_audio_OpusDecoder_init(JNIEnv *env, jobject this, jint channelcount, jint samplerate) {
|
||||
return nv_opus_init(channelcount, samplerate);
|
||||
}
|
||||
|
||||
// This function must be called after
|
||||
@ -19,24 +19,6 @@ Java_com_limelight_binding_audio_OpusDecoder_destroy(JNIEnv *env, jobject this)
|
||||
nv_opus_destroy();
|
||||
}
|
||||
|
||||
// The Opus stream is stereo
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_binding_audio_OpusDecoder_getChannelCount(JNIEnv *env, jobject this) {
|
||||
return nv_opus_get_channel_count();
|
||||
}
|
||||
|
||||
// This number assumes 2 channels at 48 KHz
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_binding_audio_OpusDecoder_getMaxOutputShorts(JNIEnv *env, jobject this) {
|
||||
return nv_opus_get_max_out_shorts();
|
||||
}
|
||||
|
||||
// The Opus stream is 48 KHz
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_binding_audio_OpusDecoder_getSampleRate(JNIEnv *env, jobject this) {
|
||||
return nv_opus_get_sample_rate();
|
||||
}
|
||||
|
||||
// outpcmdata must be 5760*2 shorts in length
|
||||
// packets must be decoded in order
|
||||
// a packet loss must call this function with NULL indata and 0 inlen
|
||||
@ -44,7 +26,7 @@ Java_com_limelight_binding_audio_OpusDecoder_getSampleRate(JNIEnv *env, jobject
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_binding_audio_OpusDecoder_decode(
|
||||
JNIEnv *env, jobject this, // JNI parameters
|
||||
jbyteArray indata, jint inoff, jint inlen, // Input parameters
|
||||
jbyteArray indata, jint inoff, jint inlen, jint framesize, // Input parameters
|
||||
jbyteArray outpcmdata) // Output parameter
|
||||
{
|
||||
jint ret;
|
||||
@ -55,13 +37,12 @@ Java_com_limelight_binding_audio_OpusDecoder_decode(
|
||||
if (indata != NULL) {
|
||||
jni_input_data = (*env)->GetByteArrayElements(env, indata, 0);
|
||||
|
||||
ret = nv_opus_decode(&jni_input_data[inoff], inlen, (jshort*) jni_pcm_data);
|
||||
ret = nv_opus_decode(&jni_input_data[inoff], inlen, framesize, (jshort*) jni_pcm_data);
|
||||
|
||||
// The input data isn't changed so it can be safely aborted
|
||||
(*env)->ReleaseByteArrayElements(env, indata, jni_input_data, JNI_ABORT);
|
||||
}
|
||||
else {
|
||||
ret = nv_opus_decode(NULL, 0, (jshort*) jni_pcm_data);
|
||||
} else {
|
||||
ret = nv_opus_decode(NULL, 0, framesize, (jshort*) jni_pcm_data);
|
||||
}
|
||||
|
||||
(*env)->ReleaseByteArrayElements(env, outpcmdata, jni_pcm_data, 0);
|
||||
|
@ -9,39 +9,40 @@ import com.limelight.nvstream.av.audio.AudioDecoderRenderer;
|
||||
*/
|
||||
public class AlsaAudioDecoderRenderer implements AudioDecoderRenderer {
|
||||
|
||||
private final static int CHANNEL_COUNT = 2;
|
||||
private final static int SAMPLE_RATE = 48000;
|
||||
|
||||
/* Number of 16 bits frames */
|
||||
private final static int FRAME_SIZE = 240;
|
||||
|
||||
private String device;
|
||||
private byte[] decodedData;
|
||||
|
||||
public AlsaAudioDecoderRenderer(String device) {
|
||||
this.device = device;
|
||||
this.decodedData = new byte[OpusDecoder.getMaxOutputShorts()*2];
|
||||
|
||||
int err;
|
||||
|
||||
err = OpusDecoder.init();
|
||||
if (err != 0) {
|
||||
throw new IllegalStateException("Opus decoder failed to initialize");
|
||||
}
|
||||
this.decodedData = new byte[FRAME_SIZE * CHANNEL_COUNT * 2];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean streamInitialize() {
|
||||
return AlsaAudio.init(OpusDecoder.getChannelCount(), OpusDecoder.getSampleRate(), device) == 0;
|
||||
return OpusDecoder.init(CHANNEL_COUNT, SAMPLE_RATE) == 0 && AlsaAudio.init(CHANNEL_COUNT, SAMPLE_RATE, device) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playAudio(byte[] bytes, int offset, int length) {
|
||||
int decodeLen = OpusDecoder.decode(bytes, offset, length, decodedData);
|
||||
int decodeLen = OpusDecoder.decode(bytes, offset, length, FRAME_SIZE, decodedData);
|
||||
|
||||
if (decodeLen > 0) {
|
||||
//Value of decode is frames (shorts) decoded per channel
|
||||
decodeLen *= 2*OpusDecoder.getChannelCount();
|
||||
decodeLen *= CHANNEL_COUNT * 2;
|
||||
int rc = AlsaAudio.play(decodedData, 0, decodeLen);
|
||||
|
||||
if (rc<0)
|
||||
LimeLog.warning("Alsa error from writei: "+rc);
|
||||
else if (rc!=length/4)
|
||||
else if (rc!=decodeLen/4)
|
||||
LimeLog.warning("Alsa short write, write "+rc+" frames");
|
||||
} else {
|
||||
LimeLog.warning("Opus error from decode: "+decodeLen);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,7 @@ public class OpusDecoder {
|
||||
System.loadLibrary("nv_opus_dec");
|
||||
}
|
||||
|
||||
public static native int init();
|
||||
public static native int init(int channelCoumt, int sampleRate);
|
||||
public static native void destroy();
|
||||
public static native int getChannelCount();
|
||||
public static native int getMaxOutputShorts();
|
||||
public static native int getSampleRate();
|
||||
public static native int decode(byte[] indata, int inoff, int inlen, byte[] outpcmdata);
|
||||
public static native int decode(byte[] indata, int inoff, int inlen, int frameSize, byte[] outpcmdata);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user