public class

SilentAudioStream

extends java.lang.Object

implements AudioStream

 java.lang.Object

↳androidx.camera.video.internal.audio.SilentAudioStream

Gradle dependencies

compile group: 'androidx.camera', name: 'camera-video', version: '1.5.0-alpha01'

  • groupId: androidx.camera
  • artifactId: camera-video
  • version: 1.5.0-alpha01

Artifact androidx.camera:camera-video:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

An AudioStream that only outputs silent audio.

This class is not thread safe, it should be used on the same thread.

Summary

Constructors
publicSilentAudioStream(AudioSettings audioSettings)

Constructs the instance.

Methods
public AudioStream.PacketInforead(java.nio.ByteBuffer byteBuffer)

public voidrelease()

public voidsetCallback(AudioStream.AudioStreamCallback callback, java.util.concurrent.Executor executor)

public voidstart()

public voidstop()

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public SilentAudioStream(AudioSettings audioSettings)

Constructs the instance.

Parameters:

audioSettings: the audio settings.

Methods

public void setCallback(AudioStream.AudioStreamCallback callback, java.util.concurrent.Executor executor)

public void start()

public void stop()

public void release()

public AudioStream.PacketInfo read(java.nio.ByteBuffer byteBuffer)

Source

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.camera.video.internal.audio;

import static androidx.camera.video.internal.audio.AudioUtils.frameCountToDurationNs;
import static androidx.camera.video.internal.audio.AudioUtils.frameCountToSize;
import static androidx.camera.video.internal.audio.AudioUtils.sizeToFrameCount;
import static androidx.core.util.Preconditions.checkArgument;
import static androidx.core.util.Preconditions.checkState;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.Logger;

import java.nio.ByteBuffer;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * An AudioStream that only outputs silent audio.
 *
 * <p>This class is not thread safe, it should be used on the same thread.
 */
public class SilentAudioStream implements AudioStream {
    private static final String TAG = "SilentAudioStream";

    private final AtomicBoolean mIsStarted = new AtomicBoolean(false);
    private final AtomicBoolean mIsReleased = new AtomicBoolean(false);
    private final int mBytesPerFrame;
    private final int mSampleRate;
    @Nullable
    private byte[] mZeroBytes;
    private long mCurrentReadTimeNs;
    @Nullable
    private AudioStreamCallback mAudioStreamCallback;
    @Nullable
    private Executor mCallbackExecutor;

    /**
     * Constructs the instance.
     *
     * @param audioSettings the audio settings.
     */
    public SilentAudioStream(@NonNull AudioSettings audioSettings) {
        mBytesPerFrame = audioSettings.getBytesPerFrame();
        mSampleRate = audioSettings.getSampleRate();
    }

    @Override
    public void setCallback(@Nullable AudioStreamCallback callback, @Nullable Executor executor) {
        checkState(!mIsStarted.get(), "AudioStream can not be started when setCallback.");
        checkNotReleasedOrThrow();
        checkArgument(callback == null || executor != null,
                "executor can't be null with non-null callback.");
        mAudioStreamCallback = callback;
        mCallbackExecutor = executor;
    }

    @Override
    public void start() {
        checkNotReleasedOrThrow();
        if (mIsStarted.getAndSet(true)) {
            return;
        }
        mCurrentReadTimeNs = currentSystemTimeNs();
        notifySilenced();
    }

    @Override
    public void stop() {
        checkNotReleasedOrThrow();
        mIsStarted.set(false);
    }

    @Override
    public void release() {
        mIsReleased.getAndSet(true);
    }

    @NonNull
    @Override
    public PacketInfo read(@NonNull ByteBuffer byteBuffer) {
        checkNotReleasedOrThrow();
        checkStartedOrThrow();
        long requiredFrameCount = sizeToFrameCount(byteBuffer.remaining(), mBytesPerFrame);
        int requiredSize = (int) frameCountToSize(requiredFrameCount, mBytesPerFrame);
        if (requiredSize <= 0) {
            return PacketInfo.of(0, mCurrentReadTimeNs);
        }
        long requiredDurationNs = frameCountToDurationNs(requiredFrameCount, mSampleRate);
        long nextReadTimeNs = mCurrentReadTimeNs + requiredDurationNs;
        blockUntilSystemTimeReached(nextReadTimeNs);
        writeSilenceToBuffer(byteBuffer, requiredSize);
        PacketInfo packetInfo = PacketInfo.of(requiredSize, mCurrentReadTimeNs);
        mCurrentReadTimeNs = nextReadTimeNs;
        return packetInfo;
    }

    private void writeSilenceToBuffer(@NonNull ByteBuffer byteBuffer, int sizeInBytes) {
        checkState(sizeInBytes <= byteBuffer.remaining());
        if (mZeroBytes == null || mZeroBytes.length < sizeInBytes) {
            mZeroBytes = new byte[sizeInBytes];
        }
        int originalPosition = byteBuffer.position();
        byteBuffer.put(mZeroBytes, 0, sizeInBytes)
                .limit(originalPosition + sizeInBytes)
                .position(originalPosition);
    }

    private void notifySilenced() {
        AudioStreamCallback callback = mAudioStreamCallback;
        Executor executor = mCallbackExecutor;
        if (callback != null && executor != null) {
            executor.execute(() -> callback.onSilenceStateChanged(true));
        }
    }

    private void checkNotReleasedOrThrow() {
        checkState(!mIsReleased.get(), "AudioStream has been released.");
    }

    private void checkStartedOrThrow() {
        checkState(mIsStarted.get(), "AudioStream has not been started.");
    }

    private static long currentSystemTimeNs() {
        return System.nanoTime();
    }

    // To avoid writing silence too fast, delay a while if the current system time haven't reach
    // the next read time.
    private static void blockUntilSystemTimeReached(long nextReadTimeNs) {
        long requiredBlockTimeNs = nextReadTimeNs - currentSystemTimeNs();
        if (requiredBlockTimeNs > 0L) {
            try {
                Thread.sleep(TimeUnit.NANOSECONDS.toMillis(requiredBlockTimeNs));
            } catch (InterruptedException e) {
                Logger.w(TAG, "Ignore interruption", e);
            }
        }
    }
}