(抱歉,一遍又一遍地修复它。]
我想在停止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'
}
onMarkerReached
是一个规范很差的API;行为似乎随着OEM的变化而变化,并且据我所知,更新间隔可能长达一秒(与AudioTrack.getNotificationMarkerPosition()
的返回值一样粗)。恕我直言,onMarkerReached()
唯一可接受的用例是针对媒体播放器类型控件的定期搜索栏更新。
将您的新内容流式传输到一个单一的AudioTrack
中是一个更好的主意。换句话说,您只需启动一个AudioTrack
,让它继续播放,并确保它不会饿死(您可以在音轨中插入静音以填补任何空白)。这样,您就可以决定如何将“播放1”转换为“播放2”,并且可以精确控制时间。