/*
Copyright (c) 2004, Chris Adamson
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.oreilly.qtjnotebook.ch07;
import quicktime.*;
import quicktime.std.*;
import quicktime.std.movies.*;
import quicktime.std.movies.media.*;
import quicktime.io.*;
import quicktime.util.*;
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
public class AudioSampleBuilder extends Object {
static final int SAMPLING = 44100;
static final byte[] ONE_SECOND_SAMPLE = new byte [SAMPLING * 2];
// static final int FREQUENCY = 1000;
static final int FREQUENCY = 440; // A4 on piano
// static final int FREQUENCY = 262; // middle C
public static void main (String[] args) {
try {
QTSessionCheck.check();
QTFile movFile = new QTFile (new java.io.File("buildaudio.mov"));
Movie movie =
Movie.createMovieFile(movFile,
StdQTConstants.kMoviePlayer,
StdQTConstants.createMovieFileDeleteCurFile |
StdQTConstants.createMovieFileDontCreateResFile);
System.out.println ("Created Movie");
// create an empty audio track
int timeScale = SAMPLING; // 44100 units per second
Track soundTrack = movie.addTrack (0, 0, 1);
System.out.println ("Added empty Track");
// create media for this track
Media soundMedia = new SoundMedia (soundTrack,
timeScale);
System.out.println ("Created Media");
// add samples
soundMedia.beginEdits();
// formats at
// http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/soundformats.htm
// "twos" is k16BitBigEndianFormat
// int format = QTUtils.toOSType ("twos");
int format = QTUtils.toOSType ("NONE");
// int format = QTUtils.toOSType ("raw "); // oops, 8 bit
SoundDescription soundDesc = new SoundDescription(format);
System.out.println ("Created SoundDescription");
soundDesc.setNumberOfChannels(1);
soundDesc.setSampleSize(16);
soundDesc.setSampleRate(SAMPLING);
for (int i=0; i<5; i++) {
// build the one-second sample
QTHandle mediaHandle = buildOneSecondSample (i);
System.out.println ("mediaHandle length = " +
mediaHandle.getSize());
soundMedia.addSample(mediaHandle, // QTHandleRef data,
0, // int dataOffset,
mediaHandle.getSize(), // int dataSize,
1, // int durationPerSample,
soundDesc, // SampleDescription sampleDesc,
SAMPLING, // int numberOfSamples,
0 // int sampleFlags)
);
}
// finish editing and insert media into track
soundMedia.endEdits();
System.out.println ("Ended edits");
soundTrack.insertMedia (0, // trackStart
0, // mediaTime
soundMedia.getDuration(), // mediaDuration
1); // mediaRate
System.out.println ("inserted media");
// save up
System.out.println ("Saving...");
OpenMovieFile omf = OpenMovieFile.asWrite (movFile);
movie.addResource (omf,
StdQTConstants.movieInDataForkResID,
movFile.getName());
System.out.println ("Done");
System.exit(0);
} catch (QTException qte) {
qte.printStackTrace();
}
} // main
/** Fill ONE_SECOND_SAMPLE with two-byte samples, according
to some scheme (like square wave, sine wave, etc.)
then wrap with QTHandle
*/
public static QTHandle buildOneSecondSample (int inTime)
throws QTException {
// convert inTime to sample count (ie, how many samples
// past 0 we are)
int wavelengthInSamples = SAMPLING / FREQUENCY;
System.out.println ("wavelengthInSamples is " +
wavelengthInSamples);
int halfWavelength = wavelengthInSamples / 2;
int sample = inTime * SAMPLING;
for (int i=0; i<SAMPLING*2; i+=2) {
int offset = sample % wavelengthInSamples;
// square wave - bytes are either 7fff or 0000
if (offset < halfWavelength) {
ONE_SECOND_SAMPLE[i] = (byte) 0x7f;
ONE_SECOND_SAMPLE[i+1] = (byte) 0xff;
} else {
ONE_SECOND_SAMPLE[i] = (byte) 0x00;
ONE_SECOND_SAMPLE[i+1] = (byte) 0x00;
}
sample ++;
}
return new QTHandle (ONE_SECOND_SAMPLE);
}
/** Fill ONE_SECOND_SAMPLE with two-byte samples, according
to some scheme (like square wave, sine wave, etc.)
then wrap with QTHandle
THIS IS AN ALTERNATE VERSION THAT PRODUCES A SINE WAVE
INSTEAD OF THE SQUARE WAVE SHOWN IN THE BOOK
public static QTHandle buildOneSecondSample (int inTime)
throws QTException {
// convert inTime to sample count (ie, how many samples
// past 0 we are)
int wavelengthInSamples = SAMPLING / FREQUENCY;
System.out.println ("wavelengthInSamples is " +
wavelengthInSamples);
int sample = inTime * SAMPLING;
double twoPi = 2 * Math.PI;
double radiansPerSample = twoPi / wavelengthInSamples;
// each sample should be one n/th of twoPi
for (int i=0; i<SAMPLING*2; i+=2) {
int offset = sample % wavelengthInSamples;
// sine wave
double angle = offset * radiansPerSample;
double sine = Math.sin (angle);
// sines are -1<x<1. we want from 0 to 0x7fff
double heightD = (sine + 1) * (0x7fff / 2);
// don't need to fix endianness because these are shorts, not ints
// short height = EndianOrder.flipNativeToBigEndian16 ((short) heightD);
short height = (short) heightD;
// System.out.println (Integer.toString(height));
// pack this into array as two bytes
ONE_SECOND_SAMPLE [i] = (byte) ((height & 0xff00) >> 8);
ONE_SECOND_SAMPLE [i+1] = (byte) (height & 0xff);
sample ++;
}
return new QTHandle (ONE_SECOND_SAMPLE);
}
*/
}
|