Android左右声道的控制

简介: 效果图源码源码下载,请先移步Android左右声道的控制我这里主要是用到了AudioTrack实现的左右声道的控制,手机一般都只有两个声道,即左声道和右声道,我们在输出的时候可以选择单声道,也可以选择双声道(立体声)。

效果图

效果图

源码

源码下载,请先移步Android左右声道的控制

我这里主要是用到了AudioTrack实现的左右声道的控制,手机一般都只有两个声道,即左声道和右声道,我们在输出的时候可以选择单声道,也可以选择双声道(立体声)。

查看了AudioTrack的API,提供了play()pause()stop()write()等一系列的方法。
通过write()方法,可以实现将音频数据发送出去(播放出来)。

AudioTrack对象的构造

有三个构造方法

AudioTrack (int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)
AudioTrack (int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode, int sessionId)
AudioTrack (AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId)

主要参数有如下几个

  • streamType:以什么形式播放

    • STREAM_VOICE_CALL
    • STREAM_SYSTEM
    • STREAM_RING
    • STREAM_MUSIC
    • STREAM_ALARM
    • STREAM_NOTIFICATION
  • sampleRateInHz:采样率

  • channelConfig:声道

    • AudioFormat.CHANNEL_OUT_MONO:输出单声道音频数据
    • AudioFormat.CHANNEL_OUT_STEREO:输出双声道音频数据(立体声)
  • audioFormat:音频数据格式

  • mode:缓冲模式

    • MODE_STATIC:一次性将音频载入以后再播放
    • MODE_STREAM:以流的形式,加载一点就播放一点

把channelConfig的相关参数都看了一遍,没发现有可以指定向某声道发送数据的,只能通过AudioFormat.CHANNEL_OUT_MONOAudioFormat.CHANNEL_OUT_STEREO选择是输出单声道的音频数据还是双声道的音频数据。

左右声道控制

构造的时候不能选择指定声道输出音频,但是有这样一个方法

setStereoVolume(float leftGain, float rightGain)

可以通过把某一个声道的音量设置到最小,达到只想某个声道输出音频的效果。
我自己也有点”呵呵“,但是也没有发现还有别的方法可以实现这样的效果。

这个方法还有一点小问题,在个别手机上,即使将某个声道的声音设置到了最小,也还是会有一点声音,这个我也还没有搞清楚为什么,个人猜测可能和手机硬件有关系。

封装

我这里的缓冲模式使用的MODE_STREAM的形式,以流的形式播放,因为这个逻辑要稍微复杂一点,尤其是暂停以后再继续播放的位置。

package kong.qingwei.androidsoundmanagerdemo;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Created by kqw on 2016/8/26.
 * 播放音乐的线程
 */
public class PlayThread extends Thread {

    // 采样率
    private int mSampleRateInHz = 16000;
    // 单声道
    private int mChannelConfig = AudioFormat.CHANNEL_OUT_MONO;
    // 双声道(立体声)
    // private int mChannelConfig = AudioFormat.CHANNEL_OUT_STEREO;

    private static final String TAG = "PlayThread";
    private Activity mActivity;
    private AudioTrack mAudioTrack;
    private byte[] data;
    private String mFileName;

    public PlayThread(Activity activity, String fileName) {
        mActivity = activity;
        mFileName = fileName;

        int bufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, AudioFormat.ENCODING_PCM_16BIT);
        mAudioTrack = new AudioTrack(
                AudioManager.STREAM_MUSIC,
                mSampleRateInHz,
                mChannelConfig,
                AudioFormat.ENCODING_PCM_16BIT,
                bufferSize,
                AudioTrack.MODE_STREAM);
    }

    @Override
    public void run() {
        super.run();
        try {
            if (null != mAudioTrack)
                mAudioTrack.play();

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            InputStream inputStream = mActivity.getResources().getAssets().open(mFileName);

            // 缓冲区
            byte[] buffer = new byte[1024];
            // 播放进度
            int playIndex = 0;
            // 是否缓冲完成
            boolean isLoaded = false;
            // 缓冲 + 播放
            while (null != mAudioTrack && AudioTrack.PLAYSTATE_STOPPED != mAudioTrack.getPlayState()) {
                // 字符长度
                int len;
                if (-1 != (len = inputStream.read(buffer))) {
                    byteArrayOutputStream.write(buffer, 0, len);
                    data = byteArrayOutputStream.toByteArray();
                    Log.i(TAG, "run: 已缓冲 : " + data.length);
                } else {
                    // 缓冲完成
                    isLoaded = true;
                }

                if (AudioTrack.PLAYSTATE_PAUSED == mAudioTrack.getPlayState()) {
                    // TODO 已经暂停
                }
                if (AudioTrack.PLAYSTATE_PLAYING == mAudioTrack.getPlayState()) {
                    Log.i(TAG, "run: 开始从 " + playIndex + " 播放");
                    playIndex += mAudioTrack.write(data, playIndex, data.length - playIndex);
                    Log.i(TAG, "run: 播放到了 : " + playIndex);
                    if (isLoaded && playIndex == data.length) {
                        Log.i(TAG, "run: 播放完了");
                        mAudioTrack.stop();
                    }

                    if (playIndex < 0) {
                        Log.i(TAG, "run: 播放出错");
                        mAudioTrack.stop();
                        break;
                    }
                }
            }
            Log.i(TAG, "run: play end");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置左右声道平衡
     *
     * @param max     最大值
     * @param balance 当前值
     */
    public void setBalance(int max, int balance) {
        float b = (float) balance / (float) max;
        Log.i(TAG, "setBalance: b = " + b);
        if (null != mAudioTrack)
            mAudioTrack.setStereoVolume(1 - b, b);
    }

    /**
     * 设置左右声道是否可用
     *
     * @param left  左声道
     * @param right 右声道
     */
    public void setChannel(boolean left, boolean right) {
        if (null != mAudioTrack) {
            mAudioTrack.setStereoVolume(left ? 1 : 0, right ? 1 : 0);
            mAudioTrack.play();
        }
    }

    public void pause() {
        if (null != mAudioTrack)
            mAudioTrack.pause();
    }

    public void play() {
        if (null != mAudioTrack)
            mAudioTrack.play();
    }

    public void stopp() {
        releaseAudioTrack();
    }

    private void releaseAudioTrack() {
        if (null != mAudioTrack) {
            mAudioTrack.stop();
            mAudioTrack.release();
            mAudioTrack = null;
        }
    }
}

使用

从头开始播放

mPlayThread = new PlayThread(this, "tts1.pcm");
mPlayThread.start();

暂停

mPlayThread.pause();

暂停后继续播放

mPlayThread.play();

停止播放

mPlayThread.stopp();
mPlayThread = null;

左右声道控制

// 禁用左声道(右声道同理)
mPlayThread.setChannel(false, true);

向左右声道单独输出不同的音频数据

也是一个很”呵呵“的做法,但是依然还没有找到更好的方法。
构造两个AudioTrack对象,分别输出两个音频,一个禁用左声道,一个禁用右声道,达到预期效果。

mChannelLeftPlayer = new PlayThread(this, "tts1.pcm");
mChannelRightPlayer = new PlayThread(this, "tts2.pcm");

mChannelLeftPlayer.setChannel(true, false);
mChannelRightPlayer.setChannel(false, true);

mChannelLeftPlayer.start();
mChannelRightPlayer.start();
相关文章
|
Android开发 缓存
Android输出正弦波音频信号(左右声道对称)
转载请说明出处! 作者:kqw攻城狮 出处:个人站 | CSDN 需求:左右声道分别输出不同的音频数据,波形要是一个正弦波,左右声道还要对称! 对硬件不是很了解,说是要通过音波避障。
1413 0
|
8月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1432 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
8月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1018 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
8月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1119 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
9月前
|
开发工具 Android开发
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
883 11
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
|
8月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
389 0
|
9月前
|
Java 开发工具 Maven
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
1180 6
|
11月前
|
安全 数据库 Android开发
在Android开发中实现两个Intent跳转及数据交换的方法
总结上述内容,在Android开发中,Intent不仅是活动跳转的桥梁,也是两个活动之间进行数据交换的媒介。运用Intent传递数据时需注意数据类型、传输大小限制以及安全性问题的处理,以确保应用的健壯性和安全性。
664 11
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
4940 77
|
11月前
|
移动开发 Java 编译器
Kotlin与Jetpack Compose:Android开发生态的演进与架构思考
本文从资深Android工程师视角深入分析Kotlin与Jetpack Compose在Android系统中的技术定位。Kotlin通过空安全、协程等特性解决了Java在移动开发中的痛点,成为Android官方首选语言。Jetpack Compose则引入声明式UI范式,通过重组机制实现高效UI更新。两者结合不仅提升开发效率,更为跨平台战略和现代架构模式提供技术基础,代表了Android开发生态的根本性演进。
446 0

热门文章

最新文章