Android AudioTrack.onPlaybackPositionUpdateListener 并不总是按时触发

Android AudioTrack.onPlaybackPositionUpdateListener not always firing on time

提问人:Nerdy Bunz 提问时间:5/17/2018 更新时间:6/2/2022 访问量:1547

问:

我注意到 AudioTrack.setPlaybackPositionUpdateListener() 有时似乎无法按预期工作(至少在 API 19-22 的 Android Studio 模拟器中)。

我做了一个小测试程序,它有一个按钮,当按下它时,开始向 AudioTrack 提供一秒钟长的音频缓冲区。

AudioTrack 应该使用 onPeriodicNotification() 进行回调,这反过来又会翻转 Activity 的背景颜色并生成 Log。

预期行为:

按下开始按钮后,或多或少每秒发送和接收一次通知。

这确实在大多数时候发生,但有时(似乎主要在 API 19-22 上):

第一个(一秒钟后)通知被遗漏/推迟,取而代之的是,我们在两秒钟内同时收到两个通知。

为什么会这样?有没有更好的方法可以根据播放头位置获得回调?

我能想到的唯一原因可能是 CPU 频率缩放问题,如本视频的 19:30 左右所述。

主要活动:

public class MainActivity extends Activity {

    View rootView;
    Button startButton;
    int initialBackgroundColor;
    boolean colorFlipper = false;
    boolean isPlaying = false;

    // AUDIO -------------
    AudioTrack audioTrack;
    int sampleRateInHz = 44100;
    int bufferSizeInBytes = 44100;
    byte[] silenceArray;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        rootView = findViewById(R.id.rootView);
        startButton = findViewById(R.id.startButton);
        initialBackgroundColor = rootView.getDrawingCacheBackgroundColor();

        // AUDIO -------------
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes, AudioTrack.MODE_STREAM);
        audioTrack.setPositionNotificationPeriod(44100); // this amounts to one second


        // create "dummy" (silent) sound... one second long
        silenceArray = new byte[bufferSizeInBytes];
        for (int i = 0; i < (bufferSizeInBytes-1); i++)
            silenceArray[i] = 0;

    }

    public void startPressed(View v) {
        boolean wasPlayingWhenPressed = startButton.isSelected();
        startButton.setSelected(!startButton.isSelected());
        if (wasPlayingWhenPressed) {
            stop();
            startButton.setText("START");
        } else {
            start();
            startButton.setText("STOP");
        }
    }

    private void start() {
        isPlaying = true;
        audioTrack.reloadStaticData();
        audioTrack.play();

        Runnable r = new Runnable() {
            public void run() {

                audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener(){
                    @Override
                    public void onMarkerReached(AudioTrack arg0) {
                    }
                    @Override
                    public void onPeriodicNotification(AudioTrack arg0) {
                        // this *should* be called every second after play is pressed,
                        // but sometimes the first call is postponed and comes out
                        // simultaneously with the second call
                        Log.i("XXX","onPeriodicNotification() was called");
                        flipBackgroundColor();
                    }
                });

                while(isPlaying) {
                    audioTrack.write(silenceArray,0,silenceArray.length);
                }
            }
        };

        Thread backround_thread = new Thread(r);
        backround_thread.start();
    }

    private void stop() {

        isPlaying = false;
        audioTrack.stop();

    }

    private void flipBackgroundColor(){

        colorFlipper = !colorFlipper;
        if (colorFlipper) {
            rootView.setBackgroundColor(Color.RED);
        } else {
            rootView.setBackgroundColor(initialBackgroundColor);
        }

    }

}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:id="@+id/rootView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/startButton"
        android:onClick="startPressed"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="start"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.boober.stackqaudiotrackcallback"
        minSdkVersion 19
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
Android 多线程音频 回调 音轨

评论

1赞 Joao 6/1/2022
我来晚了一点。我遇到了完全相同的问题。1 秒音频轨道缓冲区,48k 采样率,总录制时间 5 秒,音频格式 16 位 PCM,以 1 声道 (MONO) 录制和播放。这意味着帧大小为 16 位,记录的总帧数为 240000(超过 5 秒)。但是,在播放时,我的听众只收到 194880 帧的通知(就像您一样,我也记录了它)。奇怪的是,在我上次日志之后,android 本身说:“D/AudioTrack:stop(17435):调用了 240000 帧”
0赞 Joao 6/1/2022
尝试创建我自己的 HandlerThread 并使用其 Looper,但仍然得到完全相同的结果。
0赞 Joao 6/1/2022
我的音频没有被缩短。它发挥了100%。只有通知侦听器不会收到所有帧的通知。奇怪的行为
0赞 Joao 6/1/2022
4 年后问这个问题有点奇怪,但你有没有设法解决这个问题?谢谢
2赞 Nerdy Bunz 6/2/2022
我只能通过在第一秒到期时自己在 momemt 手动触发回调来解决它。如果我还记得,这个问题影响了 API 19-22,无论是模拟器还是真实设备,但我必须重新审视它。

答: 暂无答案