├── .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 | --------------------------------------------------------------------------------