Ignore:
Timestamp:
03/08/16 23:22:09 (8 years ago)
Author:
davidb
Message:

Generalization of audio support to allow playback/mixer to be stereo, plus some edits to comments

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/apollo/audio/ApolloPlaybackMixer.java

    r375 r1007  
    324324                if (playbackThread == null) {
    325325               
    326                         playbackThread = new PlaybackThread();
     326                        AudioFormat audio_format = SampledAudioManager.getInstance().getDefaultPlaybackFormat();
     327                        if (audio_format.getChannels()==2) {
     328                                playbackThread = new StereoPlaybackThread();
     329                        }
     330                        else {
     331                                playbackThread = new MonoPlaybackThread();
     332                        }
    327333                        playbackThread.start();
    328334                       
     
    451457         *
    452458         */
    453         private class PlaybackThread extends Thread {
    454                
    455                 private SourceDataLine srcDataLine; // never null
    456 
    457                 private boolean isStopping = false;
    458                
    459                 private int bufferFrameLength;
    460                 private boolean isOutputBigEndian;
     459        private abstract class PlaybackThread extends Thread {
     460               
     461                protected SourceDataLine srcDataLine; // never null
     462
     463                protected boolean isStopping = false;
     464               
     465                protected int bufferFrameLength;
     466                protected boolean isOutputBigEndian;
    461467               
    462468                /**
     
    465471                 * @throws LineUnavailableException
    466472                 */
    467                 PlaybackThread() throws LineUnavailableException {
    468                         super("Apollo Playback Mixer Thread");
     473                protected PlaybackThread(String threadName) throws LineUnavailableException {
     474                        super(threadName);
    469475                        super.setPriority(Thread.MAX_PRIORITY);
    470476                       
     
    494500                 *              If failed to acquire the source data line.
    495501                 */
    496                 private void aquireSourceDataLine() throws LineUnavailableException {
     502                protected void aquireSourceDataLine() throws LineUnavailableException {
    497503
    498504                        // Select an audio output format
     
    508514                       
    509515                        // Cache useful data
    510                         bufferFrameLength = srcDataLine.getBufferSize() / 2;
     516                        bufferFrameLength = srcDataLine.getBufferSize() / (2*2); // 2=stereo, 2=16-bit
    511517                        isOutputBigEndian = srcDataLine.getFormat().isBigEndian();
    512518                       
     
    527533               
    528534                /**
    529                  * Note: even if all tracks have been proccessed in the audio pipeline, it will
     535                 * Note: even if all tracks have been processed in the audio pipeline, it will
    530536                 * commence another pass to check for new tracks added to the graph before finishing.
    531537                 *
     
    540546                 * @return the best audio format for playback...
    541547                 */
    542                 private AudioFormat getAudioFormat() {
     548                protected AudioFormat getAudioFormat() {
    543549                        return SampledAudioManager.getInstance().getDefaultPlaybackFormat();
    544550                }
    545551
     552                /**
     553                 * The audio mixing pipeline
     554                 */
     555                public abstract void run();
     556       
     557
     558               
     559        }
     560
     561    private class StereoPlaybackThread extends PlaybackThread {
     562               
     563
     564               
     565                /**
     566                 * Initantly prepares for audio playback: Opens the (stereo) source data line for output
     567                 *
     568                 * @throws LineUnavailableException
     569                 */
     570                StereoPlaybackThread() throws LineUnavailableException {
     571                        super("Apollo Stereo Playback Mixer Thread");
     572                       
     573                }
     574               
     575       
     576                /**
     577                 * The audio mixing pipeline
     578                 */
     579                public void run() {
     580
     581                        // Notify observers that some audio has started playing
     582                        ApolloPlaybackMixer.this.fireSubjectChangedLaterOnSwingThread(
     583                                new SubjectChangedEvent(ApolloSubjectChangedEvent.PLAYBACK_STARTED));
     584                       
     585                        // All tracks to play per pass
     586                        List<TrackSequence> tracksToPlay = new LinkedList<TrackSequence>();
     587                       
     588                        // Keeps track of tracks to remove
     589                        List<TrackSequence> completedTracks = new LinkedList<TrackSequence>();
     590                       
     591                        // The buffer written directly to the source data line
     592                        byte[] sampleBuffer = new byte[2 * 2 * bufferFrameLength]; // 2=stereo, 2=16-bit samples
     593                       
     594                        // The mixed frames, where each element refers to a frame
     595                        int[] mixedFrameBufferL = new int[bufferFrameLength];
     596                        int[] mixedFrameBufferR = new int[bufferFrameLength];
     597                       
     598                        // Helpers declared outside loop for mz efficiency
     599                        int msbL, lsbL;
     600                        int msbR, lsbR;
     601                        int sampleL;
     602                        int sampleR;
     603                        int totalFramesMixed;
     604                        int trackCount; // tracks to play at a given pass
     605                        boolean isMoreQueued; // True if there are more tracks queued.
     606                        int frameIndex;
     607                        int i;
     608                       
     609                        // Begin writing to the source data line
     610                        if (srcDataLine.isOpen())
     611                                srcDataLine.start();
     612                        else return;
     613
     614                        // keep playing as long as line is open (and there is something to play)
     615                        try
     616                        {
     617                                while (srcDataLine.isOpen()) { // The audio mixing pipline
     618
     619                                        // First decide on which tracks to play ... and remove any finished tracks.
     620                                        synchronized(sequenceGraph) {
     621                                               
     622                                                // If there are no more tracks queued for playing, then exit the
     623                                                // playback thread.
     624                                                if (sequenceGraph.isEmpty())
     625                                                        return;
     626               
     627                                                isMoreQueued = false;
     628                                                completedTracks.clear();
     629                                                tracksToPlay.clear();
     630                                               
     631                                                for (TrackSequence ts : sequenceGraph) {
     632                                                       
     633                                                        // Has this track sequence finished?
     634                                                        if (ts.currentFrame > ts.endFrame || ts.stopPending)
     635                                                                completedTracks.add(ts);
     636                                                       
     637                                                        // Is this track playing / is meant to start laying in this pass?
     638                                                        else if (ts.initiationFrame <= (timelineFrame + bufferFrameLength))
     639                                                                tracksToPlay.add(ts);
     640       
     641                                                        // If it is not time to play the track yet, then
     642                                                        // neither will it for all proceeding tracks either
     643                                                        // since they are ordered by their initiation time.
     644                                                        else break;
     645                                                       
     646                                                }
     647                                               
     648                                                // Get rid of tracks that have finished playing. Notify models that they have stopped
     649                                                for (TrackSequence staleTS : completedTracks) {
     650                                                       
     651                                                        sequenceGraph.remove(staleTS);
     652                                                       
     653                                                        staleTS.onStopped((staleTS.currentFrame > staleTS.endFrame)
     654                                                                        ? staleTS.endFrame : staleTS.currentFrame);
     655                                                       
     656                                                        //removeTrackFromGraph(staleTS, staleTS.endFrame);
     657                                                }
     658
     659                                                trackCount = tracksToPlay.size();
     660                                                isMoreQueued = sequenceGraph.size() > trackCount;
     661                                               
     662                                                // If there is nothing queued and there are no tracks to play,
     663                                                // then playback is finished.
     664                                                if (!isMoreQueued && trackCount == 0)
     665                                                        return;
     666                                               
     667                                        } // release lock
     668                                       
     669                                        totalFramesMixed = 0; // this will be set to the maximum amount of frames that were mixed accross all tracks
     670                                       
     671                                        // Clear audio buffer
     672                                        for (i = 0; i < bufferFrameLength; i++) {
     673                                                // TODO: Efficient way of clearing buffer?
     674                                                mixedFrameBufferL[i] = 0;
     675                                                mixedFrameBufferR[i] = 0;
     676                                        }
     677                                       
     678                                        // Perform Mixing :
     679                                        // Convert the sample size to 16-bit always for best precision while
     680                                        // processing audio in the mix pipeline....
     681                                        for (TrackSequence ts : tracksToPlay) {
     682                                               
     683                                                // Notify model that initiated
     684                                                if (!ts.isPlaying()) ts.onInitiated(timelineFrame);
     685
     686                                                // Skip muted / unsoloed tracks - they add nothing to the sample mix
     687                                                if (ts.isMuted || (isSoloEnable && !ts.isSolo)) {
     688                                                       
     689                                                        // Make sure start where initiated, if not already initiated
     690                                                        if (ts.initiationFrame >= timelineFrame && ts.initiationFrame < (timelineFrame + bufferFrameLength)) {
     691                                                               
     692                                                                // Get index in frame buffer where to initiate
     693                                                                frameIndex = (int)(ts.initiationFrame - timelineFrame);
     694                                                               
     695                                                                // Calcuate the length of frames to buffer - adjust silent tracks position
     696                                                                ts.currentFrame += (bufferFrameLength - frameIndex);
     697
     698                                                        } else { // skip full buffer of bytes ... silenced
     699                                                               
     700                                                                ts.currentFrame += bufferFrameLength; // currentFrame can go outside endframe boundry of the track
     701                               
     702                                                        }
     703                                                       
     704                                                        totalFramesMixed = bufferFrameLength;
     705                                                       
     706                                                } else { // Get samples and add to mix
     707       
     708                                                        // If the track is yet to initiate - part way through the buffer, then start adding bytes
     709                                                        // at initiation point
     710                                                        if (ts.initiationFrame >= timelineFrame && ts.initiationFrame < (timelineFrame + bufferFrameLength)) {
     711                                                               
     712                                                                frameIndex = (int)(ts.initiationFrame - timelineFrame);
     713                                                               
     714                                                        } else {
     715                                                               
     716                                                                frameIndex = 0;
     717                                                               
     718                                                        }
     719               
     720                                                        // For each frame
     721                                                        for (;frameIndex < bufferFrameLength && ts.currentFrame <= ts.endFrame; frameIndex++) {
     722                                                               
     723                                                                // Get sample according to byte order
     724                                                                int base_posL = ts.currentFrame * (2*2); // 2=stereo, 2=16-bit
     725                                                                int base_posR = base_posL+2;
     726                                                               
     727                                                                if (ts.isBigEndian) {
     728                                                                       
     729                                                                        // First byte is MSB (high order)
     730                                                                        msbL = (int)ts.playbackAudioBytes[base_posL];
     731                                                                         
     732                                                                         // Second byte is LSB (low order)
     733                                                                        lsbL = (int)ts.playbackAudioBytes[base_posL + 1];
     734                                                                       
     735                                                                        // And again for the right channel
     736                                                                        msbR= (int)ts.playbackAudioBytes[base_posR];
     737                                                                        lsbR = (int)ts.playbackAudioBytes[base_posR + 1];
     738                                                               
     739                                                                 } else {
     740                                                                         
     741                                                                        // First byte is LSB (low order)
     742                                                                        lsbL = (int)ts.playbackAudioBytes[base_posL];
     743                                                                         
     744                                                                        // Second byte is MSB (high order)
     745                                                                        msbL = (int)ts.playbackAudioBytes[base_posL+1];
     746                                                                       
     747                                                                        // And again for the right channel
     748                                                                        lsbR = (int)ts.playbackAudioBytes[base_posR];
     749                                                                        msbR = (int)ts.playbackAudioBytes[base_posR+1];
     750                                                                }
     751                                                               
     752                                                                sampleL = (msbL << 0x8) | (0xFF & lsbL);
     753                                                                sampleR = (msbR << 0x8) | (0xFF & lsbR);
     754                                                               
     755                                                                // Apply track volume
     756                                                                sampleL = (int)(sampleL * ts.volume);
     757                                                                sampleR = (int)(sampleR * ts.volume);
     758                                                               
     759                                                                // Add to current mix
     760                                                                mixedFrameBufferL[frameIndex] += sampleL;
     761                                                                mixedFrameBufferR[frameIndex] += sampleR;
     762                                                               
     763                                                                // Get next sample
     764                                                                ts.currentFrame++;
     765                                                        }
     766                                                       
     767                                                       
     768                                                        // Keep track of total frames mixed in buffer
     769                                                        if (frameIndex > totalFramesMixed)
     770                                                                totalFramesMixed = frameIndex;
     771                                                }
     772               
     773                                        } // Mix in next track
     774       
     775                                        // totalFramesMixed is the amount of frames to play.
     776                                        // If it is zero then it means that there are tracks yet to be initiated, and nothing currently playing
     777                                        assert (totalFramesMixed <= bufferFrameLength);
     778                                        assert (totalFramesMixed > 0 ||
     779                                                        (totalFramesMixed == 0 && trackCount == 0 && isMoreQueued));
     780                                       
     781                                        // Post mix with master settings
     782                                        if (isMasterMuteOn) { // Silence sample buffer if master mute is on
     783       
     784                                                for (i = 0; i < sampleBuffer.length; i++) {
     785                                                        sampleBuffer[i] = 0;
     786                                                }
     787                                               
     788                                                // Let the muted bytes play
     789                                                totalFramesMixed = bufferFrameLength;
     790                                               
     791                                        } else { // otherwise apply master volume
     792                                               
     793                                                for (i = 0; i < totalFramesMixed; i++) {
     794               
     795                                                        // Average tracks
     796                                                        //mixedFrameBuffer[i] /= trackCount; // depreciated
     797                                                       
     798                                                        // Apply master volume
     799                                                        mixedFrameBufferL[i] = (int)(mixedFrameBufferL[i] * masterVolume);
     800                                                        mixedFrameBufferR[i] = (int)(mixedFrameBufferR[i] * masterVolume);
     801                                                       
     802                                                        // Clip
     803                                                        if (mixedFrameBufferL[i] > Short.MAX_VALUE) mixedFrameBufferL[i] = Short.MAX_VALUE;
     804                                                        else if (mixedFrameBufferL[i] < Short.MIN_VALUE) mixedFrameBufferL[i] = Short.MIN_VALUE;
     805                                                       
     806                                                        if (mixedFrameBufferR[i] > Short.MAX_VALUE) mixedFrameBufferR[i] = Short.MAX_VALUE;
     807                                                        else if (mixedFrameBufferR[i] < Short.MIN_VALUE) mixedFrameBufferR[i] = Short.MIN_VALUE;
     808                                                       
     809                                                        // Convert to output format
     810                                                        lsbL = (mixedFrameBufferL[i] & 0xFF);
     811                                                        msbL = ((mixedFrameBufferL[i] >> 8) & 0xFF);
     812                                                        lsbR = (mixedFrameBufferR[i] & 0xFF);
     813                                                        msbR = ((mixedFrameBufferR[i] >> 8) & 0xFF);
     814                                                       
     815                                                        int base_posL = i * (2 * 2); // 2=stereo, 2=16-bits
     816                                                        int base_posR = base_posL + 2;
     817                                                        if (isOutputBigEndian) {
     818                                                                sampleBuffer[base_posL]   = (byte)msbL;
     819                                                                sampleBuffer[base_posL+1] = (byte)lsbL;
     820                                                                sampleBuffer[base_posR]   = (byte)msbR;
     821                                                                sampleBuffer[base_posR+1] = (byte)lsbR;
     822                                                        } else {
     823                                                                sampleBuffer[base_posL]   = (byte)lsbL;
     824                                                                sampleBuffer[base_posL+1] = (byte)msbL;
     825                                                                sampleBuffer[base_posR]   = (byte)lsbR;
     826                                                                sampleBuffer[base_posR+1] = (byte)msbR;
     827                                                        }
     828
     829                                                }
     830
     831                                        }
     832                                       
     833                                        // Generate silence only if there are more tracks to be played.
     834                                        // Note that this could be false, but a track might have been queued after
     835                                        // setting the isMoreQueued flag. In such cases... silence is not wanted anyway!
     836                                        if (isMoreQueued) {
     837                                                for (i = totalFramesMixed; i < bufferFrameLength; i++) { // will skip if no need to generate silence
     838                                                        int base_posL = i * (2 * 2); // 2=stereo, 2=16-bits
     839                                                        int base_posR = base_posL + 2;
     840                                                       
     841                                                        sampleBuffer[base_posL]   = 0;
     842                                                        sampleBuffer[base_posL+1] = 0;
     843                                                        sampleBuffer[base_posR]   = 0;
     844                                                        sampleBuffer[base_posR+1] = 0;
     845                                                }
     846                                                // Ensure that full buffer is played ... including the silence
     847                                                totalFramesMixed = bufferFrameLength;
     848                                        }
     849       
     850                                        // Write processed bytes to line out stream and update the timeline frame
     851                                        srcDataLine.write(
     852                                                        sampleBuffer,
     853                                                        0,
     854                                                        totalFramesMixed * (2 * 2)); // 2=stereo, 2=16-bits
     855                                       
     856                                        // Update timeline counter for sequencing management
     857                                        timelineFrame += totalFramesMixed;
     858                                       
     859                                        // The timelineFrame should always be larger or equal to the live frame position
     860                                        assert(timelineFrame >= srcDataLine.getLongFramePosition());
     861                                       
     862                                } // Next pass
     863                       
     864                        } finally {
     865                               
     866                                isStopping = true;
     867                               
     868                                // Ensure line freed
     869                                if (srcDataLine.isOpen()) {
     870                                        srcDataLine.drain(); // avoids chopping off last buffered chunk
     871                                        srcDataLine.close();
     872                                }
     873                               
     874                                // Clear sequence graph.
     875                                synchronized(sequenceGraph) {
     876                                       
     877                                        for (TrackSequence track : sequenceGraph) {
     878                                               
     879                                                track.onStopped((track.currentFrame > track.endFrame)
     880                                                                ? track.endFrame : track.currentFrame);
     881                                        }
     882                                       
     883                                        sequenceGraph.clear();
     884                                       
     885                                }
     886
     887                                // Notify observers that playback has finished.
     888                                ApolloPlaybackMixer.this.fireSubjectChangedLaterOnSwingThread(
     889                                                new SubjectChangedEvent(ApolloSubjectChangedEvent.PLAYBACK_STOPPED));
     890                               
     891                        }
     892                       
     893                }
     894        }
     895   
     896    private class MonoPlaybackThread extends PlaybackThread {
     897               
     898
     899               
     900                /**
     901                 * Initantly prepares for audio playback: Opens the (stereo) source data line for output
     902                 *
     903                 * @throws LineUnavailableException
     904                 */
     905                MonoPlaybackThread() throws LineUnavailableException {
     906                        super("Apollo Mono Playback Mixer Thread");
     907                       
     908                }
     909               
    546910                /**
    547911                 * The audio mixing pipeline
     
    6371001                                       
    6381002                                        // Clear audio buffer
    639                                         for (i = 0; i < bufferFrameLength; i++) // TODO: Effecient way of clearing buffer?
     1003                                        for (i = 0; i < bufferFrameLength; i++) // TODO: Efficient way of clearing buffer?
    6401004                                                mixedFrameBuffer[i] = 0;
    6411005                                       
     
    7461110                                                        //mixedFrameBuffer[i] /= trackCount; // depreciated
    7471111                                                       
    748                                                         // Apply mastar volume
     1112                                                        // Apply master volume
    7491113                                                        mixedFrameBuffer[i] = (int)(mixedFrameBuffer[i] * masterVolume);
    7501114                                                       
     
    7811145                                        }
    7821146       
    783                                         // Write proccessed bytes to line out stream and update the timeline frame
     1147                                        // Write processed bytes to line out stream and update the timeline frame
    7841148                                        srcDataLine.write(
    7851149                                                        sampleBuffer,
     
    8261190                }
    8271191               
    828 
    829                
    830         }
     1192    }
    8311193
    8321194       
Note: See TracChangeset for help on using the changeset viewer.