Почему onMarkerReached не вызывают в следующей игре, если я не оставляю достаточно времени после остановки? - PullRequest
0 голосов
/ 15 февраля 2020

(Извините, что исправляю это снова и снова.)

Я хочу воспроизвести новый поток сразу после остановки AudioTrack.

Однако, если вы не запускаете воспроизведение через несколько миллисекунд после остановка воспроизведения с помощью stop () , OnMarkerReached обратный вызов не может быть вызван.

Я написал пример кода.

Нажмите кнопку, чтобы воспроизвести звук .

При нажатии кнопки данные за 0,5 секунды записываются в AudioTrack, а через 0,25 секунды сигнал в течение следующих 0,5 секунд записывается в AudioTrack с помощью onMarkerReached . Звук воспроизводится в течение 2 секунд.

Если вы нажмете кнопку во время воспроизведения звука в течение 2 секунд, он будет stop () , flu sh () и начните следующее воспроизведение. В настоящее время OnMarkerReached может не вызываться, если перед воспроизведением не вставлено дополнительное время.

Проверка была выполнена на двух android устройствах.

OnMarkerReached вызывается, даже если устройство A имеет 0 секунд дополнительного времени. Однако OnMarkerReached не вызывался, даже если у устройства B было дополнительное время 10 мс.

Почему onMarkerReached иногда не вызывается?

Вот Пример кода.

MainActivity. java

package com.example.audiotrackexample;

import androidx.appcompat.app.AppCompatActivity;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements AudioTrack.OnPlaybackPositionUpdateListener {

    final String TAG = MainActivity.class.getSimpleName();

    Button playBtn;
    Button queryBtn;
    TextView curPosTv;
    TextView curPosTv2;

    public class SinGenerator {

        float freq;
        int samplingRate;
        int sampleCount;

        public SinGenerator(float freq, int samplingRate){
            this.freq = freq;
            this.samplingRate = samplingRate;
            this.sampleCount = 0;
        }

        public short generate(){
            this.sampleCount++;
            double t = (double)freq * sampleCount / samplingRate;
            double sin = Math.sin(2.0 * Math.PI * t);
            return (short) (sin*Short.MAX_VALUE);
        }
    }

    static final int SAMPLING_RATE = 16000;
    static final int AUDIO_DATA_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    static final int CHANNEL = AudioFormat.CHANNEL_OUT_MONO;
    static final int READ_WAVE_BUFFER_SIZE = SAMPLING_RATE / 2;    // 0.5s
    static final int AUDIO_TRACK_MIN_BUFFER_SIZE = SAMPLING_RATE;
    int audioTrackBufferSize;
    AudioTrack audioTrack;
    SinGenerator sinGenerator = new SinGenerator(300, SAMPLING_RATE);
    short[] wave;
    short[] readWaveBuff = new short[READ_WAVE_BUFFER_SIZE];
    int waveReadLen = 0;
    Handler handler = new Handler(Looper.myLooper());

    void setNextWave(){
        System.arraycopy( wave, waveReadLen, readWaveBuff, 0, READ_WAVE_BUFFER_SIZE );
        waveReadLen += READ_WAVE_BUFFER_SIZE;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        playBtn = findViewById(R.id.playBtn);
        queryBtn = findViewById(R.id.queryBtn);
        curPosTv = findViewById(R.id.curPosTv);
        curPosTv2 = findViewById(R.id.curPosTv2);

        queryBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "curPlaybackPos : " + audioTrack.getPlaybackHeadPosition());
                curPosTv2.setText("curPlaybackPos : " + audioTrack.getPlaybackHeadPosition());
            }
        });

        audioTrackBufferSize = AudioTrack.getMinBufferSize(
                SAMPLING_RATE,
                CHANNEL,
                AUDIO_DATA_FORMAT);

        if (audioTrackBufferSize < AUDIO_TRACK_MIN_BUFFER_SIZE) {
            audioTrackBufferSize = AUDIO_TRACK_MIN_BUFFER_SIZE;
        }

        Log.d(TAG, "audioTrackBufferSize : " + audioTrackBufferSize);

        audioTrack = new AudioTrack(
              AudioManager.STREAM_MUSIC,
              SAMPLING_RATE,
              CHANNEL,
              AUDIO_DATA_FORMAT,
              audioTrackBufferSize,
              AudioTrack.MODE_STREAM);
        audioTrack.setPlaybackPositionUpdateListener(MainActivity.this);

        wave = new short[READ_WAVE_BUFFER_SIZE * 4];
        for (int i = 0; i < 4; i++) {
            sinGenerator = new SinGenerator(300 + i * 100, SAMPLING_RATE);
            for (int j = 0; j < READ_WAVE_BUFFER_SIZE; j++) {
                wave[i * READ_WAVE_BUFFER_SIZE + j] = sinGenerator.generate();
            }
        }

        playBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                audioTrack.stop();
                audioTrack.flush();

                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        waveReadLen = 0;
                        setNextWave();

                        audioTrack.setNotificationMarkerPosition(READ_WAVE_BUFFER_SIZE / 2);
                        audioTrack.write(readWaveBuff, 0, READ_WAVE_BUFFER_SIZE);
                        audioTrack.play();
                        curPosTv.setText("curPlaybackPos : " + audioTrack.getPlaybackHeadPosition());
                    }
                },0);        // device A is onMarkerReached called. but device B is not.
//                }, 10);        // device A is onMarkerReached called. but device B is not.
//                }, 100);     // Devices A and B call onMarkerReached.

            }
        });
    }

    @Override
    public void onMarkerReached(AudioTrack track) {

        curPosTv.setText("curPlaybackPos : " + audioTrack.getPlaybackHeadPosition());
        if(waveReadLen == wave.length) {
            audioTrack.stop();
            audioTrack.flush();
            Log.d(TAG, "finish playing");
        } else {

            setNextWave();
            audioTrack.write(readWaveBuff, 0, READ_WAVE_BUFFER_SIZE);
            int newMarkerPosition = audioTrack.getNotificationMarkerPosition();
            if (waveReadLen == wave.length) {
                newMarkerPosition += (READ_WAVE_BUFFER_SIZE / 2) + READ_WAVE_BUFFER_SIZE;
            } else {
                newMarkerPosition += READ_WAVE_BUFFER_SIZE;
            }

            audioTrack.setNotificationMarkerPosition(newMarkerPosition);
            Log.d(TAG, "curPos : " + audioTrack.getPlaybackHeadPosition() + " markerPos : " + audioTrack.getNotificationMarkerPosition());

        }
    }

    @Override
    public void onPeriodicNotification(AudioTrack track) {
    }

}

activity_main. xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:gravity="center"
    android:orientation="vertical">

    <Button
        android:id="@+id/playBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Audio Play">
    </Button>

    <TextView
        android:id="@+id/curPosTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

    </TextView>

    <Button
        android:id="@+id/queryBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Query marker position">
    </Button>

    <TextView
        android:id="@+id/curPosTv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

    </TextView>

</LinearLayout>

build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"
    defaultConfig {
        applicationId "com.example.audiotrackexample"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

1 Ответ

0 голосов
/ 15 февраля 2020

onMarkerReached - довольно плохо определенный API; поведение меняется с OEM на OEM, и, как я видел, интервалы обновления могут достигать секунды (столь же грубо, как и значения, возвращаемые из AudioTrack.getNotificationMarkerPosition()). ИМХО, единственный приемлемый вариант использования для onMarkerReached() - это периодические c обновления панели поиска для элемента управления типа медиаплеера.

Гораздо лучшая идея - просто транслировать новый контент в единый, уже существующий AudioTrack. Другими словами, вы просто запускаете один AudioTrack, оставляете его включенным и убедитесь, что он никогда не голодает (вы можете вставить тишину в дорожку, чтобы заполнить любые пробелы). Таким образом, вы решаете, как «воспроизведение 1» переходит в «воспроизведение2», и вы точно контролируете время.

...