除非我在停止播放后没有留出足够的时间,否则下一场比赛不会调用onMarkerReached吗?

问题描述 投票:0回答:1

(抱歉,一遍又一遍地修复它。]

我想在停止AudioTrack之后立即播放新的流。

但是,如果使用stop()停止播放后几毫秒后仍未开始播放,则可能不会调用OnMarkerReached回调。

我写了示例代码。

按下按钮播放声音。

按下按钮时,将0.5秒的数据写入AudioTrack,然后在0.25秒之后,通过onMarkerReached将接下来0.5秒的波形写入AudioTrack。播放的声音是2秒钟的声音。

如果在播放声音2秒钟的同时按下按钮,它将停止()冲洗()并开始下一个播放。此时,除非在播放之前插入了额外的时间,否则可能无法调用OnMarkerReached

验证是在两个android设备上进行的。

即使设备A有0秒的额外时间,也会调用

OnMarkerReached。但是,即使设备B有10ms的额外时间,也没有调用OnMarkerReached

为什么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'
}
android multithreading audio callback audiotrack
1个回答
0
投票

onMarkerReached是一个规范很差的API;行为似乎随着OEM的变化而变化,并且据我所知,更新间隔可能长达一秒(与AudioTrack.getNotificationMarkerPosition()的返回值一样粗)。恕我直言,onMarkerReached()唯一可接受的用例是针对媒体播放器类型控件的定期搜索栏更新。

将您的新内容流式传输到一个单一的AudioTrack中是一个更好的主意。换句话说,您只需启动一个AudioTrack,让它继续播放,并确保它不会饿死(您可以在音轨中插入静音以填补任何空白)。这样,您就可以决定如何将“播放1”转换为“播放2”,并且可以精确控制时间。

© www.soinside.com 2019 - 2024. All rights reserved.