1c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// found in the LICENSE file.
4c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
5c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "media/base/android/webaudio_media_codec_bridge.h"
6c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
7c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include <errno.h>
8868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include <fcntl.h>
9868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include <sys/stat.h>
10868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include <sys/types.h>
11c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include <unistd.h>
12c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include <vector>
13c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
14c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "base/android/jni_android.h"
15c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "base/android/jni_array.h"
16c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "base/android/jni_string.h"
17c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "base/basictypes.h"
18c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "base/logging.h"
19c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "base/posix/eintr_wrapper.h"
204311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch#include "base/stl_util.h"
21c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "jni/WebAudioMediaCodecBridge_jni.h"
22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "media/base/android/webaudio_media_codec_info.h"
23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
24c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
25c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)using base::android::AttachCurrentThread;
26c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
27c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)namespace media {
28c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
29c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void WebAudioMediaCodecBridge::RunWebAudioMediaCodec(
30c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    base::SharedMemoryHandle encoded_audio_handle,
31c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    base::FileDescriptor pcm_output,
32868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    uint32_t data_size) {
33c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  WebAudioMediaCodecBridge bridge(encoded_audio_handle, pcm_output, data_size);
34c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
35c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  bridge.DecodeInMemoryAudioFile();
36c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
37c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
38c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)WebAudioMediaCodecBridge::WebAudioMediaCodecBridge(
39c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    base::SharedMemoryHandle encoded_audio_handle,
40c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    base::FileDescriptor pcm_output,
41868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    uint32_t data_size)
42868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    : encoded_audio_handle_(encoded_audio_handle),
43c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pcm_output_(pcm_output.fd),
44c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      data_size_(data_size) {
45c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  DVLOG(1) << "WebAudioMediaCodecBridge start **********************"
46c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)           << " output fd = " << pcm_output.fd;
47c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
48c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
49c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)WebAudioMediaCodecBridge::~WebAudioMediaCodecBridge() {
50c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if (close(pcm_output_)) {
51c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    DVLOG(1) << "Couldn't close output fd " << pcm_output_
52c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)             << ": " << strerror(errno);
53c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  }
54868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)}
55c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
56868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)int WebAudioMediaCodecBridge::SaveEncodedAudioToFile(
57868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    JNIEnv* env,
58868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    jobject context) {
59868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // Create a temporary file where we can save the encoded audio data.
60868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  std::string temporaryFile =
61868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      base::android::ConvertJavaStringToUTF8(
62868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)          env,
63868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)          Java_WebAudioMediaCodecBridge_CreateTempFile(env, context).obj());
64868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
65868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // Open the file and unlink it, so that it will be actually removed
66868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // when we close the file.
67868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  int fd = open(temporaryFile.c_str(), O_RDWR);
68868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (unlink(temporaryFile.c_str())) {
69868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    VLOG(0) << "Couldn't unlink temp file " << temporaryFile
70868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)            << ": " << strerror(errno);
71868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  }
72868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
73868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (fd < 0) {
74868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return -1;
75868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  }
76868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
77868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // Create a local mapping of the shared memory containing the
78868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  // encoded audio data, and save the contents to the temporary file.
79868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  base::SharedMemory encoded_data(encoded_audio_handle_, true);
80868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
81868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (!encoded_data.Map(data_size_)) {
82868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    VLOG(0) << "Unable to map shared memory!";
83868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return -1;
84868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  }
85868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
86868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (static_cast<uint32_t>(write(fd, encoded_data.memory(), data_size_))
87868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      != data_size_) {
88868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    VLOG(0) << "Failed to write all audio data to temp file!";
89868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return -1;
90c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  }
91868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
92868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  lseek(fd, 0, SEEK_SET);
93868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
94868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  return fd;
95c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
96c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
97c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool WebAudioMediaCodecBridge::DecodeInMemoryAudioFile() {
98c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  JNIEnv* env = AttachCurrentThread();
99c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  CHECK(env);
100868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
101868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  jobject context = base::android::GetApplicationContext();
102868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
103868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  int sourceFd = SaveEncodedAudioToFile(env, context);
104868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
105868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (sourceFd < 0)
106868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    return false;
107868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
108c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  jboolean decoded = Java_WebAudioMediaCodecBridge_decodeAudioFile(
109c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      env,
110868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      context,
111c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      reinterpret_cast<intptr_t>(this),
112868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)      sourceFd,
113c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      data_size_);
114c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
115868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  close(sourceFd);
116868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
117868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DVLOG(1) << "decoded = " << (decoded ? "true" : "false");
118868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
119c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return decoded;
120c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
121c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
122c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void WebAudioMediaCodecBridge::InitializeDestination(
123c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    JNIEnv* env,
124c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    jobject /*java object*/,
125c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    jint channel_count,
126c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    jint sample_rate,
127c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    jlong duration_microsec) {
128c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // Send information about this audio file: number of channels,
129c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // sample rate (Hz), and the number of frames.
130c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  struct WebAudioMediaCodecInfo info = {
131c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    static_cast<unsigned long>(channel_count),
132c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    static_cast<unsigned long>(sample_rate),
133c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // The number of frames is the duration of the file
134c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // (in microseconds) times the sample rate.
135c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    static_cast<unsigned long>(
136c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        0.5 + (duration_microsec * 0.000001 *
137c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)               sample_rate))
138c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  };
139c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
140c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  DVLOG(1) << "InitializeDestination:"
141c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)           << "  channel count = " << channel_count
142c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)           << "  rate = " << sample_rate
143c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)           << "  duration = " << duration_microsec << " microsec";
144c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
145c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  HANDLE_EINTR(write(pcm_output_, &info, sizeof(info)));
146c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
147c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
148c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void WebAudioMediaCodecBridge::OnChunkDecoded(
149c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    JNIEnv* env,
150c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    jobject /*java object*/,
151c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    jobject buf,
1524311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    jint buf_size,
1534311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    jint input_channel_count,
1544311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    jint output_channel_count) {
155c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
156c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if (buf_size <= 0 || !buf)
157c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return;
158c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
159c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  int8_t* buffer =
160c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      static_cast<int8_t*>(env->GetDirectBufferAddress(buf));
161c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  size_t count = static_cast<size_t>(buf_size);
1624311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch  std::vector<int16_t> decoded_data;
1634311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch
1644311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch  if (input_channel_count == 1 && output_channel_count == 2) {
1654311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    // See crbug.com/266006.  The file has one channel, but the
1664311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    // decoder decided to return two channels.  To be consistent with
1674311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    // the number of channels in the file, only send one channel (the
1684311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    // first).
1694311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    int16_t* data = static_cast<int16_t*>(env->GetDirectBufferAddress(buf));
1704311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    int frame_count  = buf_size / sizeof(*data) / 2;
1714311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch
1724311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    decoded_data.resize(frame_count);
1734311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    for (int k = 0; k < frame_count; ++k) {
1744311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch      decoded_data[k] = *data;
1754311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch      data += 2;
1764311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    }
1774311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    buffer = reinterpret_cast<int8_t*>(vector_as_array(&decoded_data));
1784311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    DCHECK(buffer);
1794311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch    count = frame_count * sizeof(*data);
1804311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch  }
181c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
182c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // Write out the data to the pipe in small chunks if necessary.
183c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  while (count > 0) {
184c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    int bytes_to_write = (count >= PIPE_BUF) ? PIPE_BUF : count;
185c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    ssize_t bytes_written = HANDLE_EINTR(write(pcm_output_,
186c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                               buffer,
187c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                               bytes_to_write));
188c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if (bytes_written == -1)
189c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      break;
190c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    count -= bytes_written;
191c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    buffer += bytes_written;
192c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  }
193c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
194c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
195c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool WebAudioMediaCodecBridge::RegisterWebAudioMediaCodecBridge(JNIEnv* env) {
196c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return RegisterNativesImpl(env);
197c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
198c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
199c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} // namespace
200