├── .gitignore
├── FFmpegWrapper
├── .gitignore
├── build.gradle
├── jni
│ ├── Android.mk
│ ├── Application.mk
│ ├── FFmpegWrapper.c
│ └── ndk-build.sh
├── libs
│ └── WHEREARETHELIBS.md
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── net
│ │ └── openwatch
│ │ └── ffmpegwrapper
│ │ └── FFmpegWrapper.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_launcher.png
│ ├── drawable-mdpi
│ └── ic_launcher.png
│ ├── drawable-xhdpi
│ └── ic_launcher.png
│ ├── values-v11
│ └── styles.xml
│ ├── values-v14
│ └── styles.xml
│ └── values
│ ├── strings.xml
│ └── styles.xml
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # FFmpeg shared libraries
2 | FFmpegWrapper/libs/armeabi
3 |
4 | # Result of ndk-build.sh
5 | FFmpegWrapper/libs/armeabi.jar
6 |
7 | local.properties
8 | *.iml
9 | gen
10 | .gradle/
11 | lint.xml
12 | .DS_STORE
13 | .idea/
14 | build
15 | *.class
16 | obj
17 |
--------------------------------------------------------------------------------
/FFmpegWrapper/.gitignore:
--------------------------------------------------------------------------------
1 | jni/ffmpeg/*
2 | jni/include/*
3 | /build
4 |
--------------------------------------------------------------------------------
/FFmpegWrapper/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:1.0.0'
7 | }
8 | }
9 | apply plugin: 'com.android.library'
10 |
11 | repositories {
12 | mavenCentral()
13 | }
14 |
15 | android {
16 | compileSdkVersion 21
17 | buildToolsVersion "21.1.2"
18 |
19 | defaultConfig {
20 | minSdkVersion 8
21 | targetSdkVersion 21
22 | versionCode 1
23 | versionName '1.0'
24 | }
25 | }
26 |
27 | dependencies {
28 | compile fileTree(dir: 'libs', include: '*.jar')
29 | }
30 |
--------------------------------------------------------------------------------
/FFmpegWrapper/jni/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH := $(call my-dir)
2 |
3 | include $(CLEAR_VARS)
4 |
5 | # TODO: Observe $(TARGET_ARCH) and adjust appropriately. For now, we only have armeabi libraries
6 |
7 | # Prebuilt FFmpeg
8 |
9 | LOCAL_MODULE:= libavcodec
10 | LOCAL_SRC_FILES:= ./ffmpeg/$(TARGET_ARCH)/libavcodec-56.so
11 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
12 | include $(PREBUILT_SHARED_LIBRARY)
13 |
14 | include $(CLEAR_VARS)
15 |
16 | LOCAL_MODULE:= libavfilter
17 | LOCAL_SRC_FILES:= ./ffmpeg/$(TARGET_ARCH)/libavfilter-5.so
18 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
19 | include $(PREBUILT_SHARED_LIBRARY)
20 |
21 | include $(CLEAR_VARS)
22 |
23 | LOCAL_MODULE:= libavformat
24 | LOCAL_SRC_FILES:= ./ffmpeg/$(TARGET_ARCH)/libavformat-56.so
25 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
26 | include $(PREBUILT_SHARED_LIBRARY)
27 |
28 | include $(CLEAR_VARS)
29 |
30 | LOCAL_MODULE:= libavutil
31 | LOCAL_SRC_FILES:= ./ffmpeg/$(TARGET_ARCH)/libavutil-54.so
32 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
33 | include $(PREBUILT_SHARED_LIBRARY)
34 |
35 | include $(CLEAR_VARS)
36 |
37 | LOCAL_MODULE:= libswresample
38 | LOCAL_SRC_FILES:= ./ffmpeg/$(TARGET_ARCH)/libswresample-1.so
39 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
40 | include $(PREBUILT_SHARED_LIBRARY)
41 |
42 | include $(CLEAR_VARS)
43 |
44 | LOCAL_MODULE:= libswscale
45 | LOCAL_SRC_FILES:= ./ffmpeg/$(TARGET_ARCH)/libswscale-3.so
46 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
47 | include $(PREBUILT_SHARED_LIBRARY)
48 |
49 | # Our Wrapper
50 |
51 | include $(CLEAR_VARS)
52 |
53 | LOCAL_LDLIBS += -llog -lz
54 | LOCAL_SHARED_LIBRARIES := libavformat libavcodec libswscale libavutil
55 | LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
56 | LOCAL_SRC_FILES := FFmpegWrapper.c
57 | ifeq ($(TARGET_ARCH),x86)
58 | LOCAL_CFLAGS := $(COMMON_FLAGS_LIST)
59 | else
60 | LOCAL_CFLAGS := -march=armv7-a -mfpu=vfp -mfloat-abi=softfp $(COMMON_FLAGS_LIST)
61 | endif
62 | LOCAL_MODULE := FFmpegWrapper
63 |
64 | include $(BUILD_SHARED_LIBRARY)
65 |
--------------------------------------------------------------------------------
/FFmpegWrapper/jni/Application.mk:
--------------------------------------------------------------------------------
1 | APP_ABI := armeabi x86
2 |
--------------------------------------------------------------------------------
/FFmpegWrapper/jni/FFmpegWrapper.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, David Brodsky. All rights reserved.
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | #include
19 | #include
20 | #include
21 | #include "libavcodec/avcodec.h"
22 | #include "libavformat/avformat.h"
23 |
24 | #define LOG_TAG "FFmpegWrapper"
25 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
26 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
27 |
28 | // Output
29 | const char *outputPath;
30 | const char *outputFormatName = "hls";
31 | int hlsSegmentDurationSec = 10;
32 | int audioStreamIndex = -1;
33 | int videoStreamIndex = -1;
34 |
35 | // Video
36 | int VIDEO_PIX_FMT = PIX_FMT_YUV420P;
37 | int VIDEO_CODEC_ID = CODEC_ID_H264;
38 | int VIDEO_WIDTH = 1280;
39 | int VIDEO_HEIGHT = 720;
40 |
41 | // Audio
42 | int AUDIO_CODEC_ID = CODEC_ID_AAC;
43 | int AUDIO_SAMPLE_FMT = AV_SAMPLE_FMT_S16;
44 | int AUDIO_SAMPLE_RATE = 44100;
45 | int AUDIO_CHANNELS = 1;
46 |
47 | AVFormatContext *outputFormatContext;
48 | AVStream *audioStream;
49 | AVStream *videoStream;
50 | AVCodec *audioCodec;
51 | AVCodec *videoCodec;
52 | AVRational *videoSourceTimeBase;
53 | AVRational *audioSourceTimeBase;
54 |
55 | AVPacket *packet; // recycled across calls to writeAVPacketFromEncodedData
56 |
57 | // Example h264 file: Used to configure AVFormatContext
58 | const char *sampleFilePath = "/sdcard/sample.ts";
59 |
60 | // Debugging
61 | int videoFrameCount = 0;
62 | int WRITE_RAW_FILE = 0; // Write raw video packets to file
63 |
64 | FILE *raw_video;
65 |
66 | // FFmpeg Utilities
67 |
68 | void init(){
69 | av_register_all();
70 | avformat_network_init();
71 | avcodec_register_all();
72 |
73 | if(WRITE_RAW_FILE){
74 | raw_video = fopen("/sdcard/raw.h264", "w");
75 | }
76 | }
77 |
78 | char* stringForAVErrorNumber(int errorNumber){
79 | char *errorBuffer = malloc(sizeof(char) * AV_ERROR_MAX_STRING_SIZE);
80 |
81 | int strErrorResult = av_strerror(errorNumber, errorBuffer, AV_ERROR_MAX_STRING_SIZE);
82 | if (strErrorResult != 0) {
83 | LOGE("av_strerror error: %d", strErrorResult);
84 | return NULL;
85 | }
86 | return errorBuffer;
87 | }
88 |
89 | void addVideoStream(AVFormatContext *dest){
90 | AVCodecContext *c;
91 | AVStream *st;
92 | AVCodec *codec;
93 |
94 | /* find the video encoder */
95 | codec = avcodec_find_encoder(VIDEO_CODEC_ID);
96 | if (!codec) {
97 | LOGI("add_video_stream codec not found, as expected. No encoding necessary");
98 | }
99 |
100 | st = avformat_new_stream(dest, codec);
101 | if (!st) {
102 | LOGE("add_video_stream could not alloc stream");
103 | }
104 |
105 | videoStreamIndex = st->index;
106 | LOGI("addVideoStream at index %d", videoStreamIndex);
107 | c = st->codec;
108 |
109 | avcodec_get_context_defaults3(c, codec);
110 |
111 | c->codec_id = VIDEO_CODEC_ID;
112 |
113 | /* Put sample parameters. */
114 | // c->bit_rate = 400000;
115 | /* Resolution must be a multiple of two. */
116 | c->width = VIDEO_WIDTH;
117 | c->height = VIDEO_HEIGHT;
118 |
119 | /* timebase: This is the fundamental unit of time (in seconds) in terms
120 | * of which frame timestamps are represented. For fixed-fps content,
121 | * timebase should be 1/framerate and timestamp increments should be
122 | * identical to 1. */
123 | c->time_base.den = 30;
124 | c->time_base.num = 1;
125 | /*
126 | c->gop_size = 12; // emit one intra frame every twelve frames at most
127 | */
128 | c->pix_fmt = VIDEO_PIX_FMT;
129 |
130 | /* Not encoding
131 | if(codec_id == CODEC_ID_H264){
132 | av_opt_set(c->priv_data, "preset", "ultrafast", 0);
133 | if(crf)
134 | av_opt_set_double(c->priv_data, "crf", crf, 0);
135 | else
136 | av_opt_set_double(c->priv_data, "crf", 24.0, 0);
137 | }
138 | */
139 |
140 | /* Some formats want stream headers to be separate. */
141 | if (dest->oformat->flags & AVFMT_GLOBALHEADER)
142 | c->flags |= CODEC_FLAG_GLOBAL_HEADER;
143 |
144 | }
145 |
146 | void addAudioStream(AVFormatContext *formatContext){
147 | AVCodecContext *codecContext;
148 | AVStream *st;
149 | AVCodec *codec;
150 |
151 | /* find the audio encoder */
152 | codec = avcodec_find_encoder(AUDIO_CODEC_ID);
153 | if (!codec) {
154 | LOGE("add_audio_stream codec not found");
155 | }
156 | //LOGI("add_audio_stream found codec_id: %d",codec_id);
157 | st = avformat_new_stream(formatContext, codec);
158 | if (!st) {
159 | LOGE("add_audio_stream could not alloc stream");
160 | }
161 |
162 | audioStreamIndex = st->index;
163 |
164 | //st->id = 1;
165 | codecContext = st->codec;
166 | avcodec_get_context_defaults3(codecContext, codec);
167 | codecContext->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL; // for native aac support
168 | /* put sample parameters */
169 | //codecContext->sample_fmt = AV_SAMPLE_FMT_FLT;
170 | codecContext->sample_fmt = AUDIO_SAMPLE_FMT;
171 | codecContext->time_base.den = 44100;
172 | codecContext->time_base.num = 1;
173 | //c->bit_rate = bit_rate;
174 | codecContext->sample_rate = AUDIO_SAMPLE_RATE;
175 | codecContext->channels = AUDIO_CHANNELS;
176 | LOGI("addAudioStream sample_rate %d index %d", codecContext->sample_rate, st->index);
177 | //LOGI("add_audio_stream parameters: sample_fmt: %d bit_rate: %d sample_rate: %d", codec_audio_sample_fmt, bit_rate, audio_sample_rate);
178 | // some formats want stream headers to be separate
179 | if (formatContext->oformat->flags & AVFMT_GLOBALHEADER)
180 | codecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
181 | }
182 |
183 | void copyAVFormatContext(AVFormatContext **dest, AVFormatContext **source){
184 | int numStreams = (*source)->nb_streams;
185 | LOGI("copyAVFormatContext source has %d streams", numStreams);
186 | int i;
187 | for (i = 0; i < numStreams; i++) {
188 | // Get input stream
189 | AVStream *inputStream = (*source)->streams[i];
190 | AVCodecContext *inputCodecContext = inputStream->codec;
191 |
192 | // Add new stream to output with codec from input stream
193 | //LOGI("Attempting to find encoder %s", avcodec_get_name(inputCodecContext->codec_id));
194 | AVCodec *outputCodec = avcodec_find_encoder(inputCodecContext->codec_id);
195 | if(outputCodec == NULL){
196 | LOGI("Unable to find encoder %s", avcodec_get_name(inputCodecContext->codec_id));
197 | }
198 |
199 | AVStream *outputStream = avformat_new_stream(*dest, outputCodec);
200 | AVCodecContext *outputCodecContext = outputStream->codec;
201 |
202 | // Copy input stream's codecContext for output stream's codecContext
203 | avcodec_copy_context(outputCodecContext, inputCodecContext);
204 | outputCodecContext->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL;
205 |
206 | LOGI("copyAVFormatContext Copied stream %d with codec %s sample_fmt %s", i, avcodec_get_name(inputCodecContext->codec_id), av_get_sample_fmt_name(inputCodecContext->sample_fmt));
207 | }
208 | }
209 |
210 | // FFInputFile functions
211 | // Using these to deduce codec parameters from test file
212 |
213 | AVFormatContext* avFormatContextForInputPath(const char *inputPath, const char *inputFormatString){
214 | // You can override the detected input format
215 | AVFormatContext *inputFormatContext = NULL;
216 | AVInputFormat *inputFormat = NULL;
217 | //AVDictionary *inputOptions = NULL;
218 |
219 | if (inputFormatString) {
220 | inputFormat = av_find_input_format(inputFormatString);
221 | LOGI("avFormatContextForInputPath got inputFormat from string");
222 | }
223 | LOGI("avFormatContextForInputPath post av_Find_input_format");
224 | // It's possible to send more options to the parser
225 | // av_dict_set(&inputOptions, "video_size", "640x480", 0);
226 | // av_dict_set(&inputOptions, "pixel_format", "rgb24", 0);
227 | // av_dict_free(&inputOptions); // Don't forget to free
228 |
229 | LOGI("avFormatContextForInputPath pre avformat_open_input path: %s format: %s", inputPath, inputFormatString);
230 | int openInputResult = avformat_open_input(&inputFormatContext, inputPath, inputFormat, /*&inputOptions*/ NULL);
231 | LOGI("avFormatContextForInputPath avformat_open_input result: %d", openInputResult);
232 | if (openInputResult != 0) {
233 | LOGE("avformat_open_input failed: %s", stringForAVErrorNumber(openInputResult));
234 | avformat_close_input(&inputFormatContext);
235 | return NULL;
236 | }
237 |
238 | int streamInfoResult = avformat_find_stream_info(inputFormatContext, NULL);
239 | LOGI("avFormatContextForInputPath avformat_find_stream_info result: %d", streamInfoResult);
240 | if (streamInfoResult < 0) {
241 | avformat_close_input(&inputFormatContext);
242 | LOGE("avformat_find_stream_info failed: %s", stringForAVErrorNumber(openInputResult));
243 | return NULL;
244 | }
245 |
246 | LOGI("avFormatContextForInputPath Complete!");
247 | LOGI("AVInputFormat %s Stream0 codec: %s Stream1 codec: %s", inputFormatContext->iformat->name, avcodec_get_name(inputFormatContext->streams[0]->codec->codec_id), avcodec_get_name(inputFormatContext->streams[1]->codec->codec_id) );
248 | LOGI("Stream0 time_base: (num: %d, den: %d)", inputFormatContext->streams[0]->codec->time_base.num, inputFormatContext->streams[0]->codec->time_base.den);
249 | LOGI("Stream1 time_base: (num: %d, den: %d)", inputFormatContext->streams[1]->codec->time_base.num, inputFormatContext->streams[1]->codec->time_base.den);
250 | return inputFormatContext;
251 | }
252 |
253 | // FFOutputFile functions
254 |
255 | AVFormatContext* avFormatContextForOutputPath(const char *path, const char *formatName){
256 | AVFormatContext *outputFormatContext;
257 | LOGI("avFormatContextForOutputPath format: %s path: %s", formatName, path);
258 | int openOutputValue = avformat_alloc_output_context2(&outputFormatContext, NULL, formatName, path);
259 | if (openOutputValue < 0) {
260 | avformat_free_context(outputFormatContext);
261 | }
262 | return outputFormatContext;
263 | }
264 |
265 | int openFileForWriting(AVFormatContext *avfc, const char *path){
266 | if (!(avfc->oformat->flags & AVFMT_NOFILE)) {
267 | LOGI("Opening output file for writing at path %s", path);
268 | return avio_open(&avfc->pb, path, AVIO_FLAG_WRITE);
269 | }
270 | return 0; // This format does not require a file
271 | }
272 |
273 | int writeFileHeader(AVFormatContext *avfc){
274 | AVDictionary *options = NULL;
275 |
276 | // Write header for output file
277 | int writeHeaderResult = avformat_write_header(avfc, &options);
278 | if (writeHeaderResult < 0) {
279 | LOGE("Error writing header: %s", stringForAVErrorNumber(writeHeaderResult));
280 | av_dict_free(&options);
281 | }
282 | LOGI("Wrote file header");
283 | av_dict_free(&options);
284 | return writeHeaderResult;
285 | }
286 |
287 | int writeFileTrailer(AVFormatContext *avfc){
288 | if(WRITE_RAW_FILE){
289 | fclose(raw_video);
290 | }
291 | return av_write_trailer(avfc);
292 | }
293 |
294 | /////////////////////
295 | // JNI FUNCTIONS //
296 | /////////////////////
297 |
298 | /*
299 | * Prepares an AVFormatContext for output.
300 | * Currently, the output format and codecs are hardcoded in this file.
301 | */
302 | void Java_net_openwatch_ffmpegwrapper_FFmpegWrapper_prepareAVFormatContext(JNIEnv *env, jobject obj, jstring jOutputPath){
303 | init();
304 |
305 | // Create AVRational that expects timestamps in microseconds
306 | videoSourceTimeBase = av_malloc(sizeof(AVRational));
307 | videoSourceTimeBase->num = 1;
308 | videoSourceTimeBase->den = 1000000;
309 |
310 | audioSourceTimeBase = av_malloc(sizeof(AVRational));
311 | audioSourceTimeBase->num = 1;
312 | audioSourceTimeBase->den = 1000000;
313 |
314 | AVFormatContext *inputFormatContext;
315 | outputPath = (*env)->GetStringUTFChars(env, jOutputPath, NULL);
316 |
317 | outputFormatContext = avFormatContextForOutputPath(outputPath, outputFormatName);
318 | LOGI("post avFormatContextForOutputPath");
319 |
320 | // For copying AVFormatContext from sample file:
321 | /*
322 | inputFormatContext = avFormatContextForInputPath(sampleFilePath, outputFormatName);
323 | LOGI("post avFormatContextForInputPath");
324 | copyAVFormatContext(&outputFormatContext, &inputFormatContext);
325 | LOGI("post copyAVFormatContext");
326 | */
327 |
328 | // For manually crafting AVFormatContext
329 | addVideoStream(outputFormatContext);
330 | addAudioStream(outputFormatContext);
331 | av_opt_set_int(outputFormatContext->priv_data, "hls_time", hlsSegmentDurationSec, 0);
332 |
333 | int result = openFileForWriting(outputFormatContext, outputPath);
334 | if(result < 0){
335 | LOGE("openFileForWriting error: %d", result);
336 | }
337 |
338 | writeFileHeader(outputFormatContext);
339 | }
340 |
341 | /*
342 | * Override default AV Options. Must be called before prepareAVFormatContext
343 | */
344 |
345 | void Java_net_openwatch_ffmpegwrapper_FFmpegWrapper_setAVOptions(JNIEnv *env, jobject obj, jobject jOpts){
346 | // 1: Get your Java object's "jclass"!
347 | jclass ClassAVOptions = (*env)->GetObjectClass(env, jOpts);
348 |
349 | // 2: Get Java object field ids using the jclasss and field name as **hardcoded** strings!
350 | jfieldID jVideoHeightId = (*env)->GetFieldID(env, ClassAVOptions, "videoHeight", "I");
351 | jfieldID jVideoWidthId = (*env)->GetFieldID(env, ClassAVOptions, "videoWidth", "I");
352 |
353 | jfieldID jAudioSampleRateId = (*env)->GetFieldID(env, ClassAVOptions, "audioSampleRate", "I");
354 | jfieldID jNumAudioChannelsId = (*env)->GetFieldID(env, ClassAVOptions, "numAudioChannels", "I");
355 |
356 | jfieldID jHlsSegmentDurationSec = (*env)->GetFieldID(env, ClassAVOptions, "hlsSegmentDurationSec", "I");
357 |
358 | // 3: Get the Java object field values with the field ids!
359 | VIDEO_HEIGHT = (*env)->GetIntField(env, jOpts, jVideoHeightId);
360 | VIDEO_WIDTH = (*env)->GetIntField(env, jOpts, jVideoWidthId);
361 |
362 | AUDIO_SAMPLE_RATE = (*env)->GetIntField(env, jOpts, jAudioSampleRateId);
363 | AUDIO_CHANNELS = (*env)->GetIntField(env, jOpts, jNumAudioChannelsId);
364 |
365 | hlsSegmentDurationSec = (*env)->GetIntField(env, jOpts, jHlsSegmentDurationSec);
366 |
367 | // that's how easy love can be!
368 | }
369 |
370 | /*
371 | * Consruct an AVPacket from MediaCodec output and call
372 | * av_interleaved_write_frame with our AVFormatContext
373 | */
374 | void Java_net_openwatch_ffmpegwrapper_FFmpegWrapper_writeAVPacketFromEncodedData(JNIEnv *env, jobject obj, jobject jData, jint jIsVideo, jint jOffset, jint jSize, jint jFlags, jlong jPts){
375 | if(packet == NULL){
376 | packet = av_malloc(sizeof(AVPacket));
377 | LOGI("av_malloc packet");
378 | }
379 |
380 | if( ((int) jIsVideo) == JNI_TRUE ){
381 | videoFrameCount++;
382 | }
383 |
384 | // jData is a ByteBuffer managed by Android's MediaCodec.
385 | // Because the audo track of the resulting output mostly works, I'm inclined to rule out this data marshaling being an issue
386 | uint8_t *data = (*env)->GetDirectBufferAddress(env, jData);
387 |
388 | if( WRITE_RAW_FILE && ((int) jIsVideo) == JNI_TRUE ){
389 | fwrite(data, sizeof(uint8_t), (int)jSize, raw_video);
390 | }
391 |
392 | if(((int) jSize ) < 15){
393 | if( ((int) jIsVideo) == JNI_TRUE ){
394 | //LOGI("video: %d data: %s size: %d videoPacket#: %d", (int) jIsVideo, (char*)data, (int) jSize, videoFrameCount);
395 | }else{
396 | //LOGI("video: %d data: %s size: %d", (int) jIsVideo, data, (int) jSize);
397 | }
398 | //return;
399 | }
400 |
401 | av_init_packet(packet);
402 |
403 | if( ((int) jIsVideo) == JNI_TRUE){
404 | packet->stream_index = videoStreamIndex;
405 | }else{
406 | packet->stream_index = audioStreamIndex;
407 | }
408 |
409 | packet->size = (int) jSize;
410 | packet->data = data;
411 | packet->pts = (int) jPts;
412 |
413 | packet->pts = av_rescale_q(packet->pts, *videoSourceTimeBase, (outputFormatContext->streams[packet->stream_index]->time_base));
414 |
415 | /* Use this to break on specific frame */
416 | if(videoFrameCount == 3){
417 | //LOGI("break on frame");
418 | //LOGI("Payload size: %d", (int) jSize);
419 | }
420 |
421 |
422 | int writeFrameResult = av_interleaved_write_frame(outputFormatContext, packet);
423 | if(writeFrameResult < 0){
424 | LOGE("av_interleaved_write_frame video: %d pkt: %d size: %d error: %s", ((int) jIsVideo), videoFrameCount, ((int) jSize), stringForAVErrorNumber(writeFrameResult));
425 | }
426 | av_free_packet(packet);
427 | }
428 |
429 | /*
430 | * Finalize file. Basically a wrapper around av_write_trailer
431 | */
432 | void Java_net_openwatch_ffmpegwrapper_FFmpegWrapper_finalizeAVFormatContext(JNIEnv *env, jobject obj){
433 | LOGI("finalizeAVFormatContext");
434 | int writeTrailerResult = writeFileTrailer(outputFormatContext);
435 | if(writeTrailerResult < 0){
436 | LOGE("av_write_trailer error: %d", writeTrailerResult);
437 | }
438 | }
--------------------------------------------------------------------------------
/FFmpegWrapper/jni/ndk-build.sh:
--------------------------------------------------------------------------------
1 | # for Mac OS X
2 | # /bin/bash
3 | #set -x # verbose
4 |
5 | # Compile .so from .c
6 | ndk-build NDK_DEBUG=1
7 |
8 | # Generate .jar from .so
9 | cd ../libs
10 | jarRoot="./lib"
11 |
12 | shopt -s nullglob
13 | for dir in ./*/
14 | do
15 | echo $dir
16 | dirName=`basename $dir` # armeabi
17 | mkdir -p $jarRoot"/"$dirName # mkdir lib/armeabi
18 |
19 | cpSrc=$dir"*"
20 | cpDst=$jarRoot"/"$dirName
21 | cp -r $cpSrc $cpDst # cp armeabi/lib lib/armeabi
22 | jarPath=$dirName".jar"
23 | zip -r $jarPath $jarRoot # zip armeabi.jar lib/
24 | rm -r $jarRoot
25 | done
26 |
--------------------------------------------------------------------------------
/FFmpegWrapper/libs/WHEREARETHELIBS.md:
--------------------------------------------------------------------------------
1 | Please see [FFmpeg-Android](https://github.com/OnlyInAmerica/FFmpeg-Android) repository for directions on how to generate the shared libraries for this project.
2 |
--------------------------------------------------------------------------------
/FFmpegWrapper/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/FFmpegWrapper/src/main/java/net/openwatch/ffmpegwrapper/FFmpegWrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, David Brodsky. All rights reserved.
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package net.openwatch.ffmpegwrapper;
19 |
20 | import java.nio.ByteBuffer;
21 |
22 | import android.util.Log;
23 |
24 | /**
25 | * A wrapper around the FFmpeg C libraries
26 | * designed for muxing encoded AV packets
27 | * into various output formats not supported by
28 | * Android's MediaMuxer, which is currently limited to .mp4
29 | *
30 | * As this is designed to complement Android's MediaCodec class,
31 | * the only supported formats for jData in writeAVPacketFromEncodedData are:
32 | * H264 (YUV420P pixel format) / AAC (16 bit signed integer samples, one center channel)
33 | *
34 | * Methods of this class must be called in the following order:
35 | * 0. (optional) setAVOptions
36 | * 1. prepareAVFormatContext
37 | * 2. (repeat for each packet) writeAVPacketFromEncodedData
38 | * 3. finalizeAVFormatContext
39 | * @author davidbrodsky
40 | *
41 | */
42 | public class FFmpegWrapper {
43 |
44 | static {
45 | System.loadLibrary("FFmpegWrapper");
46 | }
47 |
48 | public native void setAVOptions(AVOptions jOpts);
49 | public native void prepareAVFormatContext(String jOutputPath);
50 | public native void writeAVPacketFromEncodedData(ByteBuffer jData, int jIsVideo, int jOffset, int jSize, int jFlags, long jPts);
51 | public native void finalizeAVFormatContext();
52 |
53 | /**
54 | * Used to configure the muxer's options.
55 | * Note the name of this class's fields
56 | * have to be hardcoded in the native method
57 | * for retrieval.
58 | * @author davidbrodsky
59 | *
60 | */
61 | static public class AVOptions{
62 | public int videoWidth = 1280;
63 | public int videoHeight = 720;
64 |
65 | public int audioSampleRate = 44100;
66 | public int numAudioChannels = 1;
67 |
68 | // Format specific options
69 | public int hlsSegmentDurationSec = 10;
70 |
71 | public String outputFormatName = "hls";
72 | // TODO: Provide a Map for format-specific options
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/FFmpegWrapper/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenWatch/FFmpegWrapper-Android/393eaf25b762babd5c32b55c7e1247844cc07e14/FFmpegWrapper/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FFmpegWrapper/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenWatch/FFmpegWrapper-Android/393eaf25b762babd5c32b55c7e1247844cc07e14/FFmpegWrapper/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FFmpegWrapper/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenWatch/FFmpegWrapper-Android/393eaf25b762babd5c32b55c7e1247844cc07e14/FFmpegWrapper/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FFmpegWrapper/src/main/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/FFmpegWrapper/src/main/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FFmpegWrapper/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FFmpegWrapper
3 |
4 |
--------------------------------------------------------------------------------
/FFmpegWrapper/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FFmpegWrapper-Android
2 |
3 | This is a lightweight wrapper around FFmpeg for Android.
4 |
5 | ## Work in progress
6 | This library isn't yet ready for anyone to use... yet.
7 |
8 | ## Instructions
9 |
10 | Before you get started, build FFmpeg for Android and generate a set of shared libraries and header files. [Check out our guide](https://github.com/OnlyInAmerica/FFmpeg-Android). Place the shared libraries in `libs/$PLATFORM_ARCH/` and the headers in `jni/include`.
11 |
12 | **Important:** Until the Android Gradle plugin supports the NDK, use the script: `jni/ndk-build.sh` in place of `ndk-build`.
13 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenWatch/FFmpegWrapper-Android/393eaf25b762babd5c32b55c7e1247844cc07e14/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jan 09 12:12:11 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':FFmpegWrapper'
2 |
--------------------------------------------------------------------------------