FFSampledSP
FFAudioFileReader.c
Go to the documentation of this file.
1 /*
2  * =================================================
3  * Copyright 2013 tagtraum industries incorporated
4  * This file is part of FFSampledSP.
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  */
23 
24 #include "com_tagtraum_ffsampledsp_FFAudioFileReader.h"
25 #include "FFUtils.h"
26 
27 static int CALLBACK_BUFFERSIZE = 32*1024;
28 
29 static jmethodID limit_MID = NULL;
30 static jmethodID ffAudioFileFormat_MID = NULL;
31 
37 static void init_ids(JNIEnv *env) {
38 #ifdef DEBUG
39  fprintf(stderr, "FFAudioFileReader.c init_ids(env)\n");
40 #endif
41 
42  if (!limit_MID) {
43  jclass bufferClass = NULL;
44  jclass ffAudioFileFormat_class = NULL;
45 
46  bufferClass = (*env)->FindClass(env, "java/nio/Buffer");
47  ffAudioFileFormat_class = (*env)->FindClass(env, "com/tagtraum/ffsampledsp/FFAudioFileFormat");
48 
49 #ifdef DEBUG
50  if (!bufferClass) fprintf(stderr, "Failed to find java/nio/Buffer\n");
51  if (!ffAudioFileFormat_class) fprintf(stderr, "Failed to find com/tagtraum/ffsampledsp/FFAudioFileFormat\n");
52 #endif
53 
54  limit_MID = (*env)->GetMethodID(env, bufferClass, "limit", "()I");
55  ffAudioFileFormat_MID = (*env)->GetMethodID(env, ffAudioFileFormat_class, "<init>", "(Ljava/lang/String;IFIIIFZJILjava/lang/Boolean;)V");
56 #ifdef DEBUG
57  if (!limit_MID) fprintf(stderr, "Failed to find limit method\n");
58  if (!ffAudioFileFormat_MID) fprintf(stderr, "Failed to find constructor of AudioFileFormat\n");
59 #endif
60  }
61 }
62 
66 typedef struct {
67  JNIEnv *env;
68  jobject byte_buffer;
69  int call_count;
70 } FFCallback;
71 
80 static int read_callback(void *opaque, uint8_t *buf, int size) {
81  int res = 0;
82  int availableData;
83  uint8_t *java_buffer = NULL;
84  FFCallback *callback = (FFCallback *)opaque;
85 
86  if (callback->call_count > 0) goto bail;
87  callback->call_count = 1;
88 
89  java_buffer = (uint8_t *)(*callback->env)->GetDirectBufferAddress(callback->env, callback->byte_buffer);
90  if (!java_buffer) {
91  res = -1;
92  throwIOExceptionIfError(callback->env, 1, "Failed to get address for byte buffer");
93  goto bail;
94  }
95  // get available data, i.e. the limit of the java buffer
96  availableData = (*callback->env)->CallIntMethod(callback->env, callback->byte_buffer, limit_MID);
97  // copy to c buf
98  memcpy(buf, (const uint8_t *)java_buffer, availableData);
99 
100  res = availableData;
101 
102 bail:
103 
104  return res;
105 }
106 
113 static int is_pcm(enum AVCodecID id) {
114  int res = 0;
115  switch (id) {
116  case AV_CODEC_ID_PCM_S8:
117  case AV_CODEC_ID_PCM_U8:
118  case AV_CODEC_ID_PCM_S16BE:
119  case AV_CODEC_ID_PCM_S16LE:
120  case AV_CODEC_ID_PCM_U16BE:
121  case AV_CODEC_ID_PCM_U16LE:
122  case AV_CODEC_ID_PCM_S24BE:
123  case AV_CODEC_ID_PCM_S24LE:
124  case AV_CODEC_ID_PCM_S32BE:
125  case AV_CODEC_ID_PCM_S32LE:
126  case AV_CODEC_ID_PCM_U32BE:
127  case AV_CODEC_ID_PCM_U32LE:
128  case AV_CODEC_ID_PCM_F32BE:
129  case AV_CODEC_ID_PCM_F32LE:
130  case AV_CODEC_ID_PCM_F64BE:
131  case AV_CODEC_ID_PCM_F64LE:
132  res = 1;
133  break;
134  default:
135  res = 0;
136  }
137  return res;
138 }
139 
147 static jlong duration(AVFormatContext *format_context, AVStream *stream) {
148  jlong duration_in_microseconds = -1;
149 
150  if (format_context->duration != AV_NOPTS_VALUE) {
151  int64_t micro_seconds_base = AV_TIME_BASE / 1000000; // this should be=1
152  duration_in_microseconds = (jlong)(format_context->duration / micro_seconds_base);
153  }
154 
155  if (stream->nb_frames != 0 && duration_in_microseconds <=0 && stream->codecpar->sample_rate > 0) {
156  duration_in_microseconds = stream->nb_frames * 1000000L / stream->codecpar->sample_rate;
157  }
158  return duration_in_microseconds;
159 }
160 
168 static jfloat get_frame_rate(AVStream *stream, jlong duration) {
169  jfloat frame_rate = -1;
170 
171  if (frame_rate <=0 && stream->nb_frames > 0 && duration > 0) {
172  frame_rate = stream->nb_frames * 1000000LL / (jfloat)duration;
173  }
174 
175  if (frame_rate <=0 && stream->codecpar->frame_size > 0 && stream->codecpar->sample_rate > 0) {
176  frame_rate = (jfloat)stream->codecpar->sample_rate/(jfloat)stream->codecpar->frame_size;
177  }
178 
179  if (frame_rate <=0 && stream->codecpar->frame_size == 0 && stream->codecpar->sample_rate > 0) {
180  frame_rate = (jfloat)stream->codecpar->sample_rate;
181  }
182 
183 
184 #ifdef DEBUG
185  if (stream->nb_frames > 0 && duration > 0) {
186  fprintf(stderr, "1 frame rate : %f\n", stream->nb_frames * 1000000LL / (jfloat)duration);
187  }
188  if (stream->codecpar->frame_size > 0 && stream->codecpar->sample_rate > 0) {
189  fprintf(stderr, "2 frame rate : %f\n", (jfloat)stream->codecpar->sample_rate/(jfloat)stream->codecpar->frame_size);
190  }
191  if (stream->codecpar->frame_size == 0 && stream->codecpar->sample_rate > 0) {
192  fprintf(stderr, "3 frame rate : %f\n", (jfloat)stream->codecpar->sample_rate);
193  }
194 #endif
195 
196  return frame_rate;
197 }
198 
204 static jobject create_ffaudiofileformat(JNIEnv *env, jstring url, jint codecId, jfloat sampleRate, jint sampleSize,
205  jint channels, jint frame_size, jfloat frame_rate, jboolean big_endian, jlong duration,
206  jint bitRate, jobject vbr) {
207  jclass ffAudioFileFormat_class = NULL;
208 
209  ffAudioFileFormat_class = (*env)->FindClass(env, "com/tagtraum/ffsampledsp/FFAudioFileFormat");
210 
211  /* Construct an FFAudioFileFormat object */
212  return (*env)->NewObject(env, ffAudioFileFormat_class, ffAudioFileFormat_MID, url, codecId, sampleRate, sampleSize,
213  channels, frame_size, frame_rate, big_endian, duration, bitRate, vbr);
214 }
215 
216 static int create_ffaudiofileformats(JNIEnv *env, AVFormatContext *format_context, jobjectArray *array, jstring url) {
217  int res = 0;
218  jlong duration_in_microseconds = -1;
219  jfloat frame_rate = -1;
220  jobject vbr = NULL;
221  jboolean big_endian = 1;
222  jobject audio_format = NULL;
223  jint frame_size = -1;
224  jint sample_size = -1;
225  int audio_stream_count = 0;
226  int audio_stream_number = 0;
227 
228  // count possible audio streams
229  int i;
230  for (i=0; i<format_context->nb_streams; i++) {
231  AVStream* stream = format_context->streams[i];
232  if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
233  audio_stream_count++;
234  }
235  }
236 
237 #ifdef DEBUG
238  fprintf(stderr, "Found %i audio streams.\n", audio_stream_count);
239 #endif
240 
241  // are there any audio streams at all?
242  if (audio_stream_count == 0) {
243  throwUnsupportedAudioFileExceptionIfError(env, -1, "Failed to find audio stream");
244  goto bail;
245  }
246 
247  // create output array
248  *array = (*env)->NewObjectArray(env, audio_stream_count, (*env)->FindClass(env, "javax/sound/sampled/AudioFileFormat"), NULL);
249  if (array == NULL) {
250  goto bail;
251  }
252 
253 #ifdef DEBUG
254  fprintf(stderr, "Created audio file format array.\n");
255 #endif
256 
257  // iterate over audio streams
258  for (i=0; i<format_context->nb_streams; i++) {
259  AVStream* stream = format_context->streams[i];
260  if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
261  /*
262  res = ff_open_stream(env, stream);
263  if (res) {
264  goto bail;
265  }
266  */
267 
268  // create object
269  duration_in_microseconds = duration(format_context, stream);
270  frame_rate = get_frame_rate(stream, duration_in_microseconds);
271  big_endian = ff_big_endian(stream->codecpar->codec_id);
272  if (is_pcm(stream->codecpar->codec_id)) {
273  frame_size = (stream->codecpar->bits_per_coded_sample / 8) * stream->codecpar->channels;
274  }
275  // TODO: Support VBR.
276 
277  sample_size = stream->codecpar->bits_per_raw_sample
278  ? stream->codecpar->bits_per_raw_sample
279  : stream->codecpar->bits_per_coded_sample;
280 
281  #ifdef DEBUG
282  fprintf(stderr, "stream->codecpar->bits_per_coded_sample: %i\n", stream->codecpar->bits_per_coded_sample);
283  fprintf(stderr, "stream->codecpar->bits_per_raw_sample : %i\n", stream->codecpar->bits_per_raw_sample);
284  fprintf(stderr, "stream->codecpar->bit_rate : %lli\n", stream->codecpar->bit_rate);
285  fprintf(stderr, "format_context->packet_size : %i\n", format_context->packet_size);
286  fprintf(stderr, "frames : %" PRId64 "\n", stream->nb_frames);
287  fprintf(stderr, "sample_rate: %i\n", stream->codecpar->sample_rate);
288  fprintf(stderr, "sampleSize : %i\n", stream->codecpar->bits_per_coded_sample);
289  fprintf(stderr, "channels : %i\n", stream->codecpar->channels);
290  fprintf(stderr, "frame_size : %i\n", (int)frame_size);
291  fprintf(stderr, "codec_id : %i\n", stream->codecpar->codec_id);
292  fprintf(stderr, "duration : %" PRId64 "\n", (int64_t)duration_in_microseconds);
293  fprintf(stderr, "frame_rate : %f\n", frame_rate);
294  if (big_endian) {
295  fprintf(stderr, "big_endian : true\n");
296  } else {
297  fprintf(stderr, "big_endian : false\n");
298  }
299  #endif
300  audio_format = create_ffaudiofileformat(env, url,
301  stream->codecpar->codec_id,
302  (jfloat)stream->codecpar->sample_rate,
303  sample_size,
304  stream->codecpar->channels,
305  frame_size,
306  frame_rate,
307  big_endian,
308  duration_in_microseconds,
309  stream->codecpar->bit_rate,
310  vbr);
311 
312  (*env)->SetObjectArrayElement(env, *array, audio_stream_number, audio_format);
313  audio_stream_number++;
314 
315  // clean up
316  /*
317  if (stream && stream->codec) {
318  avcodec_close(stream->codec);
319  }
320  */
321  }
322  }
323 
324 bail:
325  return res;
326 }
327 
336  JNIEXPORT jobjectArray JNICALL Java_com_tagtraum_ffsampledsp_FFAudioFileReader_getAudioFileFormatsFromURL(JNIEnv *env, jobject instance, jstring url) {
337 
338 #ifdef DEBUG
339  fprintf(stderr, "openFromUrl_1\n");
340 #endif
341 
342  int res = 0;
343  AVFormatContext *format_context = NULL;
344  jobjectArray array = NULL;
345  //AVStream *stream = NULL;
346  //int stream_index = 0;
347 
348  init_ids(env);
349 
350  const char *input_url = (*env)->GetStringUTFChars(env, url, NULL);
351  res = ff_open_format_context(env, &format_context, input_url);
352  if (res) {
353  goto bail;
354  }
355 
356  res = create_ffaudiofileformats(env, format_context, &array, url);
357  if (res) {
358  goto bail;
359  }
360 
361 bail:
362  if (format_context) {
363  avformat_close_input(&format_context);
364  }
365  (*env)->ReleaseStringUTFChars(env, url, input_url);
366 
367  return array;
368 }
369 
378  JNIEXPORT jobjectArray JNICALL Java_com_tagtraum_ffsampledsp_FFAudioFileReader_getAudioFileFormatsFromBuffer(JNIEnv *env, jobject instance, jobject byte_buffer) {
379  int res = 0;
380  AVFormatContext *format_context = NULL;
381  //AVStream *stream = NULL;
382  jobjectArray array = NULL;
383 
384  unsigned char* callbackBuffer = NULL;
385  FFCallback *callback = NULL;
386  AVIOContext *io_context;
387 
388  init_ids(env);
389 
390  callback = calloc(1, sizeof(FFCallback));
391  if (!callback) {
392  res = AVERROR(ENOMEM);
393  throwIOExceptionIfError(env, res, "Could not allocate callback.");
394  goto bail;
395  }
396  callback->env = env;
397  callback->byte_buffer = byte_buffer;
398  callback->call_count = 0;
399 
400  format_context = avformat_alloc_context();
401  if (!format_context) {
402  res = AVERROR(ENOMEM);
403  throwIOExceptionIfError(env, res, "Could not allocate format context.");
404  goto bail;
405  }
406 
407  // limit probe to less than what we read in one chunk...
408  format_context->probesize = 8*1024; // this corresponds to the Java code!
409  format_context->max_analyze_duration = 5*AV_TIME_BASE;
410 
411  callbackBuffer = (unsigned char*)av_malloc(CALLBACK_BUFFERSIZE * sizeof(uint8_t));
412  if (!callbackBuffer) {
413  res = AVERROR(ENOMEM);
414  throwIOExceptionIfError(env, res, "Could not allocate callback buffer.");
415  goto bail;
416  }
417 
418  io_context = avio_alloc_context(
419  callbackBuffer, // IOBuffer
420  CALLBACK_BUFFERSIZE, // Buffer Size (32kb corresponds to Java code)
421  0, // Write flag, only reading, so 0
422  callback, // FFCallback pointer (opaque)
423  read_callback, // Read callback
424  NULL, // Write callback
425  NULL // Seek callback
426  );
427  if (!io_context) {
428  res = AVERROR(ENOMEM);
429  throwIOExceptionIfError(env, res, "Could not allocate custom IO context.");
430  goto bail;
431  }
432  // we didn't supply a seek function in avio_alloc_context, so we need to make sure we don't seek..
433  io_context->seekable = 0;
434 
435  format_context->pb = io_context;
436 
437  res = ff_open_format_context(env, &format_context, "MemoryAVIOContext");
438  if (res) {
439  goto bail;
440  }
441 
442  res = create_ffaudiofileformats(env, format_context, &array, NULL);
443  if (res) {
444  goto bail;
445  }
446 
447 bail:
448 
449  /*
450  if (stream && stream->codec) {
451  avcodec_close(stream->codec);
452  }
453  */
454  if (format_context) {
455  AVFormatContext *s = format_context;
456  if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) || (s->flags & AVFMT_FLAG_CUSTOM_IO)) {
457  if (s->pb) {
458  avio_flush(s->pb);
459  av_free(s->pb->buffer);
460  av_free(s->pb);
461  }
462  }
463 
464  avformat_close_input(&format_context);
465  }
466  if (callback) {
467  free(callback);
468  }
469 
470  return array;
471 }
472 
473 
void throwIOExceptionIfError(JNIEnv *env, int err, const char *message)
Throws an IOException.
Definition: FFUtils.c:192
jobject byte_buffer
byte buffer passed in (read only once)
int ff_big_endian(enum AVCodecID codec_id)
Indicates whether the given id belongs to a big endian codec.
Definition: FFUtils.c:981
void throwUnsupportedAudioFileExceptionIfError(JNIEnv *env, int err, const char *message)
Throws an UnsupportedAudioFileException.
Definition: FFUtils.c:162
JNIEXPORT jobjectArray JNICALL Java_com_tagtraum_ffsampledsp_FFAudioFileReader_getAudioFileFormatsFromBuffer(JNIEnv *env, jobject instance, jobject byte_buffer)
Opens the byte buffer to determine its AudioFileFormat.
int ff_open_format_context(JNIEnv *env, AVFormatContext **format_context, const char *url)
Opens the input file/url and allocates a AVFormatContext for it, but does not open the audio stream w...
Definition: FFUtils.c:256
JNIEXPORT jobjectArray JNICALL Java_com_tagtraum_ffsampledsp_FFAudioFileReader_getAudioFileFormatsFromURL(JNIEnv *env, jobject instance, jstring url)
Opens the given URL to determine its AudioFileFormat.
JNIEnv * env
JNI environment.
Simple struct used for the read_callback of stream data.
int call_count
number of times the read_callback was called