CASampledSP
CACodecInputStream.cpp
Go to the documentation of this file.
1 /*
2  * =================================================
3  * Copyright 2011 tagtraum industries incorporated
4  * This file is part of CASampledSP.
5  *
6  * FFSampledSP is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFSampledSP is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFSampledSP; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  * =================================================
20  *
21  * @author <a href="mailto:hs@tagtraum.com">Hendrik Schreiber</a>
22  */
24 #include "CAUtils.h"
25 
26 static jfieldID nativeBufferFID = NULL;
27 static jmethodID rewindMID = NULL;
28 static jmethodID limitMID = NULL;
29 static jmethodID fillNativeBufferMID = NULL;
30 static jmethodID hasRemainingMID = NULL;
31 static jmethodID positionMID = NULL;
32 
33 
37 static OSStatus CACodecInputStream_ComplexInputDataProc (
38  AudioConverterRef inAudioConverter,
39  UInt32 *ioNumberDataPackets,
40  AudioBufferList *ioData,
41  AudioStreamPacketDescription **outDataPacketDescription,
42  void *inUserData) {
43 
44 #ifdef DEBUG
45  fprintf(stderr, "CACodecInputStream_ComplexInputDataProc\n");
46 #endif
47 
48  int res = 0;
49  CAAudioConverterIO *acio = (CAAudioConverterIO*)inUserData;
50  jobject byteBuffer;
51 
52  *ioNumberDataPackets = 0;
53  byteBuffer = acio->env->GetObjectField(acio->sourceStream, nativeBufferFID);
54 
55  // check whether we have to fill the source's native buffer
56  if (acio->env->CallBooleanMethod(byteBuffer, hasRemainingMID) == JNI_FALSE) {
57  // fill native buffer
58  acio->env->CallVoidMethod(acio->sourceStream, fillNativeBufferMID);
59  res = acio->env->ExceptionCheck();
60  if (res) {
61  goto bail;
62  }
63  }
64  if (acio->env->CallBooleanMethod(byteBuffer, hasRemainingMID) == JNI_FALSE) {
65  goto bail;
66  }
67 
68  // move position in java bytebuffer
69  acio->env->CallIntMethod(byteBuffer, positionMID, acio->sourceAudioIO->srcBufferSize);
70  ioData->mNumberBuffers = 1;
71  ioData->mBuffers[0].mNumberChannels = acio->sourceAudioIO->srcFormat.mChannelsPerFrame;
72  ioData->mBuffers[0].mDataByteSize = acio->sourceAudioIO->srcBufferSize;
73  //ioData->mBuffers[0].mDataByteSize = acio->sourceAudioIO->pktDescs[0].mDataByteSize;
74  ioData->mBuffers[0].mData = acio->sourceAudioIO->srcBuffer;
75  *ioNumberDataPackets = acio->sourceAudioIO->pos - acio->sourceAudioIO->lastPos;
76 
77  if (outDataPacketDescription != NULL) {
78  *outDataPacketDescription = acio->sourceAudioIO->pktDescs;
79  }
80 
81 bail:
82  if (res) {
83  *ioNumberDataPackets = 0;
84  }
85 
86  return res;
87 }
88 
97 JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_fillNativeBuffer(JNIEnv *env, jobject stream, jlong converterPtr) {
98 
99 #ifdef DEBUG
100  fprintf(stderr, "CACodecInputStream fillNativeBuffer\n");
101 #endif
102 
103  int res = 0;
104  int limit = 0;
105  UInt32 ioOutputDataPacketSize;
106  CAAudioConverterIO *acio = (CAAudioConverterIO*)converterPtr;
107  AudioBufferList outOutputData;
108  jobject byteBuffer = NULL;
109  acio->env = env;
110 
111  // get java-managed byte buffer reference
112  byteBuffer = env->GetObjectField(stream, nativeBufferFID);
113  if (byteBuffer == NULL) {
114  throwIOExceptionIfError(env, 1, "Failed to get native buffer for this codec");
115  goto bail;
116  }
117 
118  // get pointer to our java managed bytebuffer
119  acio->srcBuffer = (char *)env->GetDirectBufferAddress(byteBuffer);
120  acio->srcBufferSize = env->GetDirectBufferCapacity(byteBuffer);
121  if (acio->srcBuffer == NULL) {
122  throwIOExceptionIfError(env, 1, "Failed to obtain native buffer address for this codec");
123  goto bail;
124  }
125  ioOutputDataPacketSize = acio->srcBufferSize/acio->srcFormat.mBytesPerPacket;
126  outOutputData.mNumberBuffers = 1;
127  outOutputData.mBuffers[0].mNumberChannels = acio->srcFormat.mChannelsPerFrame;
128  outOutputData.mBuffers[0].mDataByteSize = acio->srcBufferSize;
129  outOutputData.mBuffers[0].mData = acio->srcBuffer;
130 
131  res = AudioConverterFillComplexBuffer(acio->acref,
132  CACodecInputStream_ComplexInputDataProc,
133  acio,
134  &ioOutputDataPacketSize,
135  &outOutputData,
136  acio->pktDescs);
137 
138  if (res) {
139  throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to fill complex audio buffer");
140  goto bail;
141  }
142 
143  // we already wrote to the buffer, now we still need to
144  // set new bytebuffer limit and position to 0.
145  acio->lastPos = acio->pos;
146  acio->pos += ioOutputDataPacketSize;
147  if (acio->srcFormat.mBytesPerPacket != 0) {
148  limit = ioOutputDataPacketSize*acio->srcFormat.mBytesPerPacket;
149  }
150  else {
151  uint i;
152  for (i=0; i<ioOutputDataPacketSize; i++) {
153  limit += acio->pktDescs[i].mDataByteSize;
154  }
155  }
156  acio->srcBufferSize = limit;
157  env->CallObjectMethod(byteBuffer, limitMID, (jint)limit);
158  env->CallObjectMethod(byteBuffer, rewindMID);
159  if (acio->sourceAudioIO->frameOffset != 0) {
160 #ifdef DEBUG
161  fprintf(stderr, "Need to adjust position to frame: %i\n", acio->sourceAudioIO->frameOffset);
162  fprintf(stderr, "acio->srcFormat.mBytesPerFrame : %i\n", acio->srcFormat.mBytesPerFrame);
163  env->CallIntMethod(byteBuffer, positionMID, acio->srcFormat.mBytesPerFrame * acio->sourceAudioIO->frameOffset);
164 #endif
165  acio->sourceAudioIO->frameOffset = 0;
166  }
167 
168 bail:
169  return;
170 }
171 
182 JNIEXPORT jlong JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_open(JNIEnv *env, jobject stream, jobject targetFormat, jobject sourceStream, jlong pointer) {
183  int res = 0;
185  jobject byteBuffer = NULL;
186  jclass audioFormatClass = NULL;
187  jmethodID sampleRateMID = NULL;
188  jmethodID channelsMID = NULL;
189  jmethodID frameSizeMID = NULL;
190  jmethodID sampleSizeInBitsMID = NULL;
191  jmethodID encodingMID = NULL;
192  jmethodID bigEndianMID = NULL;
193 
194  jclass caEncodingClass = NULL;
195  jmethodID dataFormatMID = NULL;
196  jobject targetEncoding = NULL;
197 
198  /* get method and field ids, if we don't have them already */
199  if (fillNativeBufferMID == NULL || hasRemainingMID == NULL || positionMID == NULL || nativeBufferFID == NULL || rewindMID == NULL || limitMID == NULL) {
200  jclass nativePeerInputStreamClass = env->FindClass("com/tagtraum/casampledsp/CANativePeerInputStream");
201  fillNativeBufferMID = env->GetMethodID(nativePeerInputStreamClass, "fillNativeBuffer", "()V");
202  nativeBufferFID = env->GetFieldID(nativePeerInputStreamClass, "nativeBuffer", "Ljava/nio/ByteBuffer;");
203  jclass bufferClass = env->FindClass("java/nio/Buffer");
204  hasRemainingMID = env->GetMethodID(bufferClass, "hasRemaining", "()Z");
205  positionMID = env->GetMethodID(bufferClass, "position", "(I)Ljava/nio/Buffer;");
206  rewindMID = env->GetMethodID(bufferClass, "rewind", "()Ljava/nio/Buffer;");
207  limitMID = env->GetMethodID(bufferClass, "limit", "(I)Ljava/nio/Buffer;");
208  }
209 
210  // get java-managed byte buffer reference
211  byteBuffer = env->GetObjectField(stream, nativeBufferFID);
212  if (byteBuffer == NULL) {
213  throwIOExceptionIfError(env, 1, "Failed to get native buffer for this codec");
214  goto bail;
215  }
216 
217  acio->sourceStream = env->NewGlobalRef(sourceStream);
218  acio->sourceAudioIO = (CAAudioIO*)pointer;
219  acio->pktDescs = NULL;
220  acio->env = env;
221  acio->srcBuffer = (char *)env->GetDirectBufferAddress(byteBuffer);
222  acio->srcBufferSize = env->GetDirectBufferCapacity(byteBuffer);
223  acio->cookie = NULL;
224  acio->cookieSize = 0;
225  acio->pos = 0;
226  acio->lastPos = 0;
227  acio->frameOffset = 0;
228 
229 
230  audioFormatClass = env->FindClass("javax/sound/sampled/AudioFormat");
231  sampleRateMID = env->GetMethodID(audioFormatClass, "getSampleRate", "()F");
232  channelsMID = env->GetMethodID(audioFormatClass, "getChannels", "()I");
233  frameSizeMID = env->GetMethodID(audioFormatClass, "getFrameSize", "()I");
234  sampleSizeInBitsMID = env->GetMethodID(audioFormatClass, "getSampleSizeInBits", "()I");
235  encodingMID = env->GetMethodID(audioFormatClass, "getEncoding", "()Ljavax/sound/sampled/AudioFormat$Encoding;");
236  bigEndianMID = env->GetMethodID(audioFormatClass, "isBigEndian", "()Z");
237 
238  caEncodingClass = env->FindClass("com/tagtraum/casampledsp/CAAudioFormat$CAEncoding");
239  dataFormatMID = env->GetMethodID(caEncodingClass, "getDataFormat", "()I");
240  targetEncoding = env->CallObjectMethod(targetFormat, encodingMID);
241 
242 
243  // set up the format we want to convert *to*
244  acio->srcFormat.mSampleRate = (Float64)env->CallFloatMethod(targetFormat, sampleRateMID);
245  acio->srcFormat.mChannelsPerFrame = (UInt32)env->CallIntMethod(targetFormat, channelsMID);
246  acio->srcFormat.mBitsPerChannel = (UInt32)env->CallIntMethod(targetFormat, sampleSizeInBitsMID);
247  acio->srcFormat.mFramesPerPacket = 1;
248  acio->srcFormat.mBytesPerFrame = (UInt32)env->CallIntMethod(targetFormat, frameSizeMID);
249  acio->srcFormat.mBytesPerPacket = acio->srcFormat.mBytesPerFrame;
250  acio->srcFormat.mFormatID = (UInt32)env->CallIntMethod(targetEncoding, dataFormatMID);
251  acio->srcFormat.mFormatFlags = 0;
252  acio->srcFormat.mReserved = 0;
253 
254  // massage format flags
255  if (acio->srcFormat.mFormatID == kAudioFormatLinearPCM) {
256  acio->srcFormat.mFormatFlags += env->CallBooleanMethod(targetFormat, bigEndianMID) == JNI_TRUE ? kAudioFormatFlagIsBigEndian : 0;
257  acio->srcFormat.mFormatFlags += kAudioFormatFlagIsPacked;
258  //acio->srcFormat.mFormatFlags += kAudioFormatFlagIsFloat;
259  // for now we don't support unsigned PCM
260  acio->srcFormat.mFormatFlags += kAudioFormatFlagIsSignedInteger;
261  }
262 
263  // make sure that AudioSystem#NOT_SPECIFIED (i.e. -1) is converted to 0.
264  if (acio->srcFormat.mSampleRate < 0) acio->srcFormat.mSampleRate = 0;
265  if (acio->srcFormat.mChannelsPerFrame < 0) acio->srcFormat.mChannelsPerFrame = 0;
266  if (acio->srcFormat.mBitsPerChannel < 0) acio->srcFormat.mBitsPerChannel = 0;
267  if (acio->srcFormat.mBytesPerFrame < 0) acio->srcFormat.mBytesPerFrame = 0;
268  if (acio->srcFormat.mBytesPerPacket < 0) acio->srcFormat.mBytesPerPacket = 0;
269 
270 
271  // checks - we need to make sure to not divide by zero later on
272  if (acio->srcFormat.mBytesPerFrame == 0) {
273  throwIllegalArgumentExceptionIfError(env, 1, "frameSize must be positive");
274  goto bail;
275  }
276  if (acio->srcFormat.mBytesPerPacket == 0) {
277  throwIllegalArgumentExceptionIfError(env, 1, "bytesPerPacket must be positive");
278  goto bail;
279  }
280  if (acio->srcFormat.mBitsPerChannel == 0) {
281  throwIllegalArgumentExceptionIfError(env, 1, "sampleSizeInBits must be positive");
282  goto bail;
283  }
284 
285 
286  // setup the format we want to convert *from*
287  while (acio->sourceAudioIO->srcFormat.mFormatID == 0) {
288  // we don't have the native structure yet/anymore - therefore we have to call fillBuffer at least once
289  env->CallVoidMethod(sourceStream, fillNativeBufferMID);
290  res = acio->env->ExceptionCheck();
291  if (res) {
292  goto bail;
293  }
294  }
295 
296  res = AudioConverterNew(&acio->sourceAudioIO->srcFormat, &acio->srcFormat, &acio->acref);
297  if (res) {
298  throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to create native codec");
299  goto bail;
300  }
301 
302  // TODO: Deal with AudioConverterPrimeInfo as described in ConvertFile sample code.
303 
304  /*
305  if (acio->sourceAudioIO->srcFormat.mBytesPerPacket == 0) {
306  // input format is VBR, so we need to get max size per packet
307  UInt32 size = sizeof(acio->srcSizePerPacket);
308  res = AudioConverterGetProperty(acio->acref, kAudioConverterPropertyMaximumInputPacketSize, &size, &acio->srcSizePerPacket);
309  if (res) {
310  throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to get maximum output packet size");
311  goto bail;
312  }
313  acio->srcSizePerPacket = 5793;
314  fprintf(stderr, "acio->srcSizePerPacket : %i\n", acio->srcSizePerPacket);
315  fprintf(stderr, "acio->srcBufferSize : %i\n", acio->srcBufferSize);
316  acio->numPacketsPerRead = acio->srcBufferSize / acio->srcSizePerPacket;
317  //acio->pktDescs = new AudioStreamPacketDescription [acio->numPacketsPerRead];
318  fprintf(stderr, "acio->numPacketsPerRead : %i\n", acio->numPacketsPerRead);
319  }
320  else {
321  acio->srcSizePerPacket = acio->srcFormat.mBytesPerPacket;
322  acio->numPacketsPerRead = acio->srcBufferSize / acio->srcSizePerPacket;
323  }
324  */
325 
326  // set cookie, if we have one
327  if (acio->sourceAudioIO->cookieSize > 0) {
328  res = AudioConverterSetProperty(acio->acref, kAudioConverterDecompressionMagicCookie, acio->sourceAudioIO->cookieSize, acio->sourceAudioIO->cookie);
329  if (res) {
330  throwUnsupportedAudioFileExceptionIfError(env, res, "Failed to set cookie from source.");
331  goto bail;
332  }
333  }
334 
335 bail:
336  if (res) {
337  env->DeleteGlobalRef(sourceStream);
338  if (acio->acref != NULL) {
339  AudioConverterDispose(acio->acref);
340  }
341  if (acio->pktDescs != NULL) {
342  delete acio->pktDescs;
343  }
344  delete acio;
345  }
346 
347  return (jlong)acio;
348 }
349 
357 JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_close(JNIEnv *env, jobject stream, jlong converterPtr) {
358  if (converterPtr == 0) return;
359  CAAudioConverterIO *acio = (CAAudioConverterIO*)converterPtr;
360  env->DeleteGlobalRef(acio->sourceStream);
361  if (acio->acref != NULL) {
362  int res = AudioConverterDispose(acio->acref);
363  if (res) {
364  throwIOExceptionIfError(env, res, "Failed to close codec");
365  }
366  }
367  if (acio->pktDescs != NULL) {
368  delete acio->pktDescs;
369  }
370  delete acio;
371 
372 }
373 
381 JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_reset(JNIEnv *env, jobject stream, jlong converterPtr) {
382  if (converterPtr == 0) return;
383  CAAudioConverterIO *acio = (CAAudioConverterIO*)converterPtr;
384  if (acio->acref != NULL) {
385  int res = AudioConverterReset(acio->acref);
386  if (res) {
387  throwIOExceptionIfError(env, res, "Failed to reset audio converter");
388  }
389  } else {
390  throwIOExceptionIfError(env, -1, "Failed to reset audio converter as it is NULL");
391  }
392 }
393 
UInt32 cookieSize
Cookie size.
Definition: CAUtils.h:63
void throwIllegalArgumentExceptionIfError(JNIEnv *env, int err, const char *message)
Throws an IllegalArgumentException.
Definition: CAUtils.cpp:73
AudioStreamBasicDescription srcFormat
Source format.
Definition: CAUtils.h:53
JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_reset(JNIEnv *env, jobject stream, jlong converterPtr)
Resets the converter - necessary after seek() to flush codec buffers.
UInt32 frameOffset
Frame offset (needed for seeking to the middles of a packet)
Definition: CAUtils.h:60
char * srcBuffer
Source buffer.
Definition: CAUtils.h:55
jobject sourceStream
Source stream object (Java)
Definition: CAUtils.h:72
void throwUnsupportedAudioFileExceptionIfError(JNIEnv *env, int err, const char *message)
Throws an UnsupportedAudioFileException exception.
Definition: CAUtils.cpp:39
AudioConverterRef acref
The used AudioConverter.
Definition: CAUtils.h:71
JNIEnv * env
JNI environment.
Definition: CAUtils.h:51
UInt32 srcBufferSize
Source buffer size.
Definition: CAUtils.h:56
Central context representing the native peer to the Java CACodecInputStream object.
Definition: CAUtils.h:69
JNIEXPORT jlong JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_open(JNIEnv *env, jobject stream, jobject targetFormat, jobject sourceStream, jlong pointer)
Sets up an AudioConverter to convert data to the desired format.
CAAudioIO * sourceAudioIO
CAAudioIO of the stream that we want to convert.
Definition: CAUtils.h:73
char * cookie
Cookie.
Definition: CAUtils.h:62
AudioStreamPacketDescription * pktDescs
Packet descriptions.
Definition: CAUtils.h:54
JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_close(JNIEnv *env, jobject stream, jlong converterPtr)
Closes the AudioConverter and cleans up other resources.
JNIEXPORT void JNICALL Java_com_tagtraum_casampledsp_CACodecInputStream_fillNativeBuffer(JNIEnv *env, jobject stream, jlong converterPtr)
Fill this streams native buffer by transcoding the content of the source stream&#39;s native buffer to th...
void throwIOExceptionIfError(JNIEnv *env, int err, const char *message)
Throws an IOException.
Definition: CAUtils.cpp:56
SInt64 lastPos
Last position (in packets)
Definition: CAUtils.h:59
SInt64 pos
Current position (in packets)
Definition: CAUtils.h:58
Central context representing the native peer to the Java CANativePeerInputStream object.
Definition: CAUtils.h:49