Android、AudioStreamのサンプル【七転び八起き】

独自のSoundFontエンジンを使っていたとき、この方法で鳴らしていました。
しかし、SoundFontの計算をすべてJavaで行うのは、無理があるらしく、最初の音がかならず割れてしまいます。
結局、WindowsでもAndroidでも、のちにFluidSynthを用いる方向に変更しています。
そちらは、別の記事として、掲載を予定しています。

XTAudioStream.java
package org.star_advance.mixtone.synth.audio;

import android.util.Log;

import org.star_advance.mih.midi.libs.MXQueue;
import org.star_advance.mixtone.synth.XTSynthesizer;
import org.star_advance.mixtone.synth.XTSynthesizerSetting;
import org.star_advance.mixtone.synth.oscilator.XTOscilator;

import java.util.LinkedList;

/**
 *
 * @author Syntarou YOSHIDA
 */
public class XTAudioStream{
    static final String TAG=  "XTAudioStream";

    static XTAudioStream _instance;
    XTSynthesizer _synth;
    
    public static XTAudioStream getInstance(XTSynthesizer synth) {
        if (_instance == null) {
            _instance = new XTAudioStream(synth);
        }
        return _instance;
    }

    public XTAudioStream(XTSynthesizer synth) {
        _synth = synth;
    }
    
    private AndroidSourceDataLine _sourceDL;
    public double _masterVolume = 1.0;

    private double[] _frame_left = null;
    private double[] _frame_right = null;

    Thread _renderThread = null;
    
    public boolean isReady() {
        return _renderThread != null && _sourceDL != null;
    }
    XTSynthesizerSetting setting = XTSynthesizerSetting.getSetting();

    public void startStream() {
        if (_renderThread == null) {
            _sourceDL = new AndroidSourceDataLine(_synth);
            _sourceDL.start();
            /*
            if (_copy != null) {
                Log.e(TAG, "copy was "+ _copy.size());
                for (int i = 0; i < _copy.size(); ++ i) {
                    XTOscilator osc = _copy.get(i);
                    Log.e(TAG, "Copy out [" + i + "] = " + osc.nextValueWithAmp() + " [" + osc.isFaded() +"]" + osc.isNoteOff() +", "+ osc._playKey + " loop " + osc._loop, osc._trace);
                }
            }
            if (_push != null) {
                Log.e(TAG, "push was "+ _push.size());
            }*/
            _copy = new LinkedList<>();
            _push = new MXQueue<>();
            if (_renderThread == null) {
                _renderThread = new Thread() {
                    public void run() {
                        long start = System.currentTimeMillis();
                        long require = (long)(setting.getSamplePageSize() / setting.getSampleRate()  * 1000);

                        while(true) {
                            if (_renderThread == null) {
                                break;
                            }
                            try {
                                updateBuffer();
                                long done = System.currentTimeMillis();
                                long dif  = done - start;
                                start = done;
                                if (require - dif >= 2) {
                                    //Log.e(TAG, "wait " + (require - dif));
                                    //負荷がかかって音がでなくなることを回避する
                                    //nativeメソッドがスパムに弱いので
                                    //nativeメソッドは2000バイト一気に処理して
                                    //またため込んで2000バイト一気に処理したりするので
                                    //nativeメソッドが壊れ前に、リズムがおかしいので
                                    //こちらで調整する
                                    synchronized (this) {
                                        try {
                                            wait((require - dif) / 2);
                                            start += (require - dif) / 2;
                                        }catch (InterruptedException ex) {

                                        }
                                    }
                                }
                            }catch(Throwable ex) {
                                ex.printStackTrace();
                            }
                        }
                        _renderThread = null;
                        _sourceDL.stop();
                        _sourceDL = null;
                    }
                };
                _renderThread.start();
            }
        }
    }
    
    public void stopStream() {
        if (_renderThread != null) {
            _renderThread = null;
        }
    }

    long _lastAllOff = -1;
    public boolean updateBuffer(){
        if (_sourceDL == null) {
            return false;
        }

        XTSynthesizerSetting setting = XTSynthesizerSetting.getSetting();
        int frameSize = (int)setting.getSamplePageSize();
        if (_frame_left == null || _frame_left.length != frameSize) {
            _frame_left = new double[frameSize];
            _frame_right = new double[frameSize];
        }
        while (_push.isEmpty() == false) {
            _copy.push(_push.pop());
        }
        if (setting._autoMuteMillisec > 0) {
            if (_copy.size() == 0) {
                if (_lastAllOff < 0) {
                    _lastAllOff = System.currentTimeMillis();
                    return false;
                }
                if (System.currentTimeMillis() - _lastAllOff >= setting._autoMuteMillisec) {
                    synchronized (_push) {
                        while (_copy.size() == 0 && _push.size() == 0) {
                            try {
                                _push.wait(500);
                            }catch (Throwable ex) {

                            }
                        }
                    }
                    _lastAllOff = -1;
                }
            }
            else {
                _lastAllOff = -1;
            }
        }

        _sourceDL.waitWriteBuffer();
        for(int i = 0; i < frameSize; i++) {

            double valueLeft = 0;
            double valueRight = 0;

            for (int k = 0; k < _copy.size() ;++ k) {
                XTOscilator j = _copy.get(k);

                double v = j.nextValueWithAmp() * j._volume * j._velocity;
                if (j.isFaded()) {
                    _copy.remove(k);
                    k --;
                    continue;
                }
                double pan = j._pan;
                if (j._type == 2) {
                    pan = 1;
                }else if (j._type == 4) {
                    pan = -1;
                }
                pan += 1;
                pan /= 2;
                double right = pan, left = 1 - pan;

                valueRight += v / 2 * right;
                valueLeft += v / 2 * left;
            }

            _frame_left[i] = valueLeft;
            _frame_right[i] = valueRight;
        }

        _sourceDL.write(_frame_right, _frame_left);
        return true;
    }

    LinkedList<XTOscilator> _copy = new LinkedList<>();
    MXQueue<XTOscilator> _push = new MXQueue<>();

    public void push(XTOscilator osc) {
        _push.push(osc);
    }

    public void clearCopy() {
        _copy.clear();
    }
}

AndroidSourceDataLine.java
package org.star_advance.mixtone.synth.audio;

import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;

import org.star_advance.mixandcc.MainActivity;

import org.star_advance.mih.midi.libs.MXQueue;
import org.star_advance.mixtone.synth.XTSynthesizer;
import org.star_advance.mixtone.synth.XTSynthesizerSetting;
import org.star_advance.mixtone.synth.oscilator.XTFilter;

public class AndroidSourceDataLine {
    XTSynthesizerSetting setting = XTSynthesizerSetting.getSetting();
    public AndroidSourceDataLine(XTSynthesizer synth) {
        _synth = synth;
    }
    public void stop() {
        _synth.allNoteOff();
        pauseThread();;
    }

    XTSynthesizer _synth;

    public void start() {
        if (_audioTrack == null) {
            int CONTENT_TYPE = AudioAttributes.CONTENT_TYPE_MUSIC;
            int CONTENT_USAGE = AudioAttributes.USAGE_MEDIA;
            int STREAM_TYPE = AudioManager.STREAM_MUSIC;

            AudioManager am = MainActivity._audioManager;

            int MODE = AudioTrack.MODE_STREAM;

            AudioFormat.Builder format = new AudioFormat.Builder();
            format.setEncoding(AudioFormat.ENCODING_PCM_16BIT);
            format.setSampleRate((int)setting.getSampleRate());
            format.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO);

            AudioAttributes.Builder attributes = new AudioAttributes.Builder();
            attributes.setContentType(CONTENT_TYPE);
            attributes.setUsage(CONTENT_USAGE);

            AudioTrack.Builder builder = new AudioTrack.Builder();

            builder.setAudioFormat(format.build());
            builder.setTransferMode(MODE);
            builder.setAudioAttributes(attributes.build());
            builder.setBufferSizeInBytes(setting.getSamplePageSize() * setting.getSamplePageCount() * 2);

            _audioTrack = builder.build();
            //_audioTrack.setPositionNotificationPeriod(_bufferSize * _bufferCount * 2);
            _audioTrack.play();
        }else {
            _audioTrack.play();
        }

        if (_audioThread == null) {
            _audioThread = new Thread() {
                @Override
                public void run() {
                    if (true) {
                        //開幕ノイズが生成される端末にたいする対処
                        short[] dummy = new short[setting.getSamplePageSize() * setting.getSamplePageCount()  * 2];
                        int x = _audioTrack.write(dummy, 0, dummy.length);
                    }
                    short[] sBuffer = new short[setting.getSamplePageSize() *2 /*_bufferCount*/];
                    int pos = 0;
                    try {
                        while (_audioThread != null) {
                            while (_pool.isEmpty()) {
                                sumREADWAIT ++;
                                synchronized (_pool) {
                                    try {
                                        _pool.wait(500);
                                    }catch(InterruptedException ex) {

                                    }
                                }
                            }
                            AudioSegment p = _pool.pop();

                            for (int i = 0; i < p._right.length; i++) {
                                short left = (short) (p._left[i] * (10000 - 2));
                                short right = (short) (p._right[i] * (10000 - 2));
                                sBuffer[pos++] = left;
                                sBuffer[pos++] = right;
                            }

                            if ( _audioTrack.getBufferSizeInFrames() < pos) {
                                Log.e(TAG, "write error ?" + _audioTrack.getAudioAttributes());
                            }
                            if (pos >= 0/*sBuffer.length*/) {
                                int x = _audioTrack.write(sBuffer, 0, pos);
                                if (x != pos) {
                                    sumFLUSHERROR ++;
                                    _synth.allNoteOff();
                                    _audioTrack.flush();
                                    Log.e(TAG, "write error " + x + " != " + pos + ", " + _audioTrack.getAudioAttributes());
                                }
                                pos = 0;
                            }
                            _trashCan.push(p);
                            synchronized (_pool) {
                                _pool.notifyAll();
                            }
                        }
                    }catch (Throwable ex) {
                        Log.e(TAG, ex.getMessage(), ex);
                    }finally {
                        Log.e(TAG, "terminated");
                        if (_audioTrack != null) {
                            _audioTrack.stop();
                            _audioTrack.flush();
                            _audioTrack.release();
                            _audioTrack = null;
                        }
                        _audioThread = null;
                    }
                }
            };
            _audioThread.start();
        }
    }

    double _div = 10000.0;
    MXQueue<AudioSegment> _pool = new MXQueue<>();
    MXQueue<AudioSegment> _trashCan = new MXQueue<>();
    class AudioSegment {
        public AudioSegment() {
            int size = setting.getSamplePageSize();
            _right = new double[size];
            _left = new double[size];
        }

        double[] _right;
        double[] _left;
    }


    long sumREADWAIT = 0;
    long sumOK = 0;
    long sumWRITEWAIT = 0;
    long sumFLUSHERROR = 0;
    long prevSec = 0;

    int suspend = 0;
    public void waitWriteBuffer() {
        synchronized (_pool) {
            while (true) {
                /*
                long nowSec = System.currentTimeMillis() / 1000;
                if (nowSec != prevSec) {
                    Log.e(TAG, " OK=" + sumOK + " writeWAIT=" + sumWRITEWAIT+ " readWAIT="+ sumREADWAIT + " FLUSHError=" + sumFLUSHERROR+ " avail=" + _audioTrack.getBufferSizeInFrames());
                    if (sumWRITEWAIT == 0) {
                        suspend ++;
                        if (suspend > 3) {
                            _audioTrack.flush();
                        }
                    }
                    else {
                        suspend = 0;
                    }
                    sumOK = 0;
                    sumWRITEWAIT = 0;
                    sumREADWAIT = 0;
                    prevSec = nowSec;
                }*/
                int cnt = _pool.size();
                if (cnt < setting.getSamplePageCount() ) {
                    sumOK ++;
                    return;
                }
                else {
                    sumWRITEWAIT++;
                }
                try {
                    _pool.wait(500);
                }catch (InterruptedException ex) {
                    break;
                }
            }
        }
    }

    public void write(double[] right, double[] left) {
        for (int i = 0; i < right.length;++ i) {
            double r = right[i] * _div;
            double l = left[i] * _div;
            if (r <= -0.9 || r >= 0.9) {
                _div *= 0.8;
                i--;
                continue;
            }
            if (l <= -0.9 || l >= 0.9) {
                _div *= 0.8;
                i--;
                continue;
            }
        }
        AudioSegment seg = null;
        synchronized (_trashCan) {
            if (!_trashCan.isEmpty()) {
                seg = _trashCan.pop();
                if (seg._right.length != right.length) {
                    seg = null;
                }
            }
        }
        if (seg == null) {
            seg = new AudioSegment();
        }
        int x = 0;
        for (int i = 0; i < right.length ;++ i) {
            double r = right[i] * _div;
            double l = left[i] * _div;
            r = _filterR.update(r);
            l = _filterL.update(l);
            seg._left[x] = l;
            seg._right[x] = r;
            x ++;
        }
        synchronized (_pool) {
            _pool.push(seg);
            _pool.notifyAll();
        }
    }
    XTFilter _filterL = new XTFilter();
    XTFilter _filterR = new XTFilter();

    static String TAG = "AndroidSourceDataLine";

    private AudioTrack _audioTrack;
    private Thread _audioThread;

    public boolean isRunning() {
        return (_audioThread != null);
    }
    public void pauseThread() {
        _audioThread = null;
        _pool = new MXQueue<>();
        _trashCan = new MXQueue<>();
        _filterL = new XTFilter();
        _filterR = new XTFilter();
        while(_audioTrack != null) {
            try {
                Thread.sleep(10);
            }
            catch (Throwable ex) {

            }
        }
    }
}

おまけとして、WindowsなどPCのJavaのソースコードです。

JavaSourceDataLine.java
package org.star_advance.mixtone.synth.audio;

import java.util.LinkedList;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import org.star_advance.libs.MXQueue;
import org.star_advance.mixtone.synth.XTSynthesizerSetting;

import org.star_advance.mixtone.synth.oscilator.XTFilter;

public class JavaSourceDataLine {
    public void stop() {
        pauseThread();
    }
    
    public void close() {
        pauseThread();
    }

    public synchronized void launchThread() {
        if (_audioTrack == null) {
            try {
                AudioFormat frmt= new AudioFormat(
                        _setting.getSampleRate(), 
                        _setting.getSampleBits(),
                        _setting.getSampleChannel(),
                        true,
                        false);
                DataLine.Info info= new DataLine.Info(SourceDataLine.class,frmt);
                SourceDataLine dataLine = (SourceDataLine) AudioSystem.getLine(info);
                dataLine.flush();
                dataLine.open(frmt, _setting.getSamplePageSize() * _setting.getSamplePageCount());
                dataLine.addLineListener(new LineListener() {
                    @Override
                    public void update(LineEvent event) {
                    }
                });
                _audioTrack = dataLine;
            } catch (LineUnavailableException e) {
                System.out.println("cant get line///");
                throw new RuntimeException(e);
            }
            _audioTrack.flush();
            _audioTrack.start();
        }

        if (_audioThread == null) {
            _audioThread = new Thread() {
                @Override
                public void run() {
                    boolean needReboot = false;
                    _setting.clearUpdated();
                    int frame_size = _setting.getSamplePageSize();
                    byte[] _stereo = new byte[frame_size /** setting.getSamplePageCount() */* 4];
                    if (_audioThread == null) {
                        try {
                            Thread.sleep(10);
                        }catch(Throwable ex) {
                            ex.printStackTrace();
                        }
                    }
                    if (_audioTrack == null) {
                        System.err.println("Launch fatal error");
                    }
                    try {
                        while (_audioThread != null) {
                            if (_setting.isUpdated()) {
                                needReboot = true;
                                break;
                            }
                            //_pool.waitForCount(setting.getSamplePageCount());
                            
                            int pos = 0;
                            //for (int i = 0; i < setting.getSamplePageCount(); ++ i) {
                                AudioSegment p = _pool.pop();
                                for (int x = 0; x < frame_size; x ++) {
                                    long sampleleft = (long)(p._left[x] * 30000);
                                    long sampleright = (long)(p._right[x] * 30000);
                                    _stereo[pos ++] = (byte)(sampleleft  & 0xff);
                                    _stereo[pos ++] = (byte)((sampleleft >> 8)  & 0xff);
                                    _stereo[pos ++] = (byte)(sampleright  & 0xff);
                                    _stereo[pos ++] = (byte)((sampleright >> 8)  & 0xff);
                                }
                                _trashCan.push(p);
                            //}
                            //if (pos >= _stereo.length) {
                                _audioTrack.write(_stereo,0,pos);
                                pos = 0;
                            //}
                        }
                    }finally {
                        if (_audioTrack != null) {
                            _audioTrack.stop();
                            _audioTrack = null;
                        }
                        _audioThread = null;
                    }
                    if (needReboot) {
                        JavaSourceDataLine.this.launchThread();
                    }
                }
            };
            _audioThread.setDaemon(true);
            _audioThread.start();
        }

    }

    public int available() {
        int x= (_setting.getSamplePageCount()- _pool.size()) * _setting.getSamplePageSize();
        if (x < 0) {
            return 0;
        }
        return x;
    }

    MXQueue<AudioSegment> _pool = new MXQueue<>();
    MXQueue<AudioSegment> _trashCan = new MXQueue<>();
    XTSynthesizerSetting _setting = XTSynthesizerSetting.getSetting();

    class AudioSegment {
        public AudioSegment() {
            int size = _setting.getSamplePageSize();
            _pos = 0;
            _right = new double[size];
            _left = new double[size];
        }

        int _pos;
        double[] _right;
        double[] _left;

        public void write(double r, double l) {
            _right[_pos] = r;
            _left[_pos] = l;
            _pos ++;
        }
    }

    public void write(double[] right, double[] left) {
        if (right.length == left.length) {
            write(right, left, 0, right.length);
        }else{
            throw  new IllegalArgumentException();
        }
    }

    double _mum = 32768;
    
    public void write(double[] right, double[] left, int pos, int length) {
        double x = 0;
        for (int i = pos; i < pos + length ;++ i) {
            double r = right[i] * _mum;
            double l = left[i] * _mum;
            if (r >= 0.8 || r <= -0.8 || l >= 0.8 || l <= -0.8) {
                _mum *= 0.8;
                i --;
                continue;
            }
            if (r < 0) {
                x -= r;
            }
            else {
                x += r;
            }
        }

        AudioSegment seg;
        if (!_trashCan.isEmpty()) {
            seg = _trashCan.pop();
            if (seg._right.length != length) {
                seg = new AudioSegment();
            }
        }
        else {
            seg = new AudioSegment();
        }

        int pen = 0;
        for (int i = pos; i < pos + length ;++ i) {
            double r = right[i] * _mum;
            double l = left[i] * _mum;
            r = _filterR.update(r);
            l = _filterL.update(l);
            seg._left[pen] = l;
            seg._right[pen] = r;
            pen ++;
        }
        _pool.push(seg);
    }
    XTFilter _filterL = new XTFilter();
    XTFilter _filterR = new XTFilter();

    private Thread _audioThread;
    private SourceDataLine _audioTrack;

    public boolean isRunning() {
        return (_audioThread != null);
    }

    public void pauseThread() {
        _audioThread = null;
        while(_audioThread != null) {
            try {
                Thread.sleep(10);
            }
            catch (Throwable ex) {

            }
        }
    }

    static String TAG = "AndroidSourceDataLine";
}
広告を表示しています。

コメント

タイトルとURLをコピーしました