From 5dd3f0eb8a7d75bef1adc288c859758361433333 Mon Sep 17 00:00:00 2001 From: xufulong <839789740@qq.com> Date: Sun, 8 Dec 2019 23:29:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=A4=E7=A7=8D=E6=96=B9=E5=BC=8F=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8moov:FFmpeg=E5=91=BD=E4=BB=A4=E8=A1=8C=E5=92=8Cfast=5F?= =?UTF-8?q?start=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两种方式移动moov:FFmpeg命令行和fast_start代码 --- app/CMakeLists.txt | 1 + app/src/main/cpp/fast_start.c | 660 ++++++++++++++++++ .../main/java/com/frank/ffmpeg/FFmpegCmd.java | 18 + .../ffmpeg/activity/VideoHandleActivity.java | 46 +- .../com/frank/ffmpeg/util/FFmpegUtil.java | 12 + .../java/com/frank/ffmpeg/util/FileUtil.java | 20 +- .../main/res/layout/activity_video_handle.xml | 7 + app/src/main/res/values/strings.xml | 2 + 8 files changed, 755 insertions(+), 11 deletions(-) create mode 100644 app/src/main/cpp/fast_start.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 634f0d3..f656acb 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -31,6 +31,7 @@ add_library( # Sets the name of the library. src/main/cpp/media_player.c src/main/cpp/video_filter.c src/main/cpp/audio_lame.c + src/main/cpp/fast_start.c ) add_library( ffmpeg diff --git a/app/src/main/cpp/fast_start.c b/app/src/main/cpp/fast_start.c new file mode 100644 index 0000000..56041fc --- /dev/null +++ b/app/src/main/cpp/fast_start.c @@ -0,0 +1,660 @@ +// +// Created by frank on 2019-12-06. +// + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __MINGW32__ +#undef fseeko +#define fseeko(x, y, z) fseeko64(x, y, z) +#undef ftello +#define ftello(x) ftello64(x) +#elif defined(_WIN32) +#undef fseeko +#define fseeko(x, y, z) _fseeki64(x, y, z) +#undef ftello +#define ftello(x) _ftelli64(x) +#endif + +#define MIN(a,b) ((a) > (b) ? (b) : (a)) + +#define BE_32(x) (((uint32_t)(((uint8_t*)(x))[0]) << 24) | \ + (((uint8_t*)(x))[1] << 16) | \ + (((uint8_t*)(x))[2] << 8) | \ + ((uint8_t*)(x))[3]) + +#define BE_64(x) (((uint64_t)(((uint8_t*)(x))[0]) << 56) | \ + ((uint64_t)(((uint8_t*)(x))[1]) << 48) | \ + ((uint64_t)(((uint8_t*)(x))[2]) << 40) | \ + ((uint64_t)(((uint8_t*)(x))[3]) << 32) | \ + ((uint64_t)(((uint8_t*)(x))[4]) << 24) | \ + ((uint64_t)(((uint8_t*)(x))[5]) << 16) | \ + ((uint64_t)(((uint8_t*)(x))[6]) << 8) | \ + ((uint64_t)( (uint8_t*)(x))[7])) + +#define AV_WB32(p, val) { \ + ((uint8_t*)(p))[0] = ((val) >> 24) & 0xff; \ + ((uint8_t*)(p))[1] = ((val) >> 16) & 0xff; \ + ((uint8_t*)(p))[2] = ((val) >> 8) & 0xff; \ + ((uint8_t*)(p))[3] = (val) & 0xff; \ + } + +#define AV_WB64(p, val) { \ + AV_WB32(p, (val) >> 32) \ + AV_WB32(p + 4, val) \ + } + +#define BE_FOURCC(ch0, ch1, ch2, ch3) \ + ( (uint32_t)(unsigned char)(ch3) | \ + ((uint32_t)(unsigned char)(ch2) << 8) | \ + ((uint32_t)(unsigned char)(ch1) << 16) | \ + ((uint32_t)(unsigned char)(ch0) << 24) ) + +#define QT_ATOM BE_FOURCC +/* top level atoms */ +#define FREE_ATOM QT_ATOM('f', 'r', 'e', 'e') +#define JUNK_ATOM QT_ATOM('j', 'u', 'n', 'k') +#define MDAT_ATOM QT_ATOM('m', 'd', 'a', 't') +#define MOOV_ATOM QT_ATOM('m', 'o', 'o', 'v') +#define PNOT_ATOM QT_ATOM('p', 'n', 'o', 't') +#define SKIP_ATOM QT_ATOM('s', 'k', 'i', 'p') +#define WIDE_ATOM QT_ATOM('w', 'i', 'd', 'e') +#define PICT_ATOM QT_ATOM('P', 'I', 'C', 'T') +#define FTYP_ATOM QT_ATOM('f', 't', 'y', 'p') +#define UUID_ATOM QT_ATOM('u', 'u', 'i', 'd') + +#define CMOV_ATOM QT_ATOM('c', 'm', 'o', 'v') +#define TRAK_ATOM QT_ATOM('t', 'r', 'a', 'k') +#define MDIA_ATOM QT_ATOM('m', 'd', 'i', 'a') +#define MINF_ATOM QT_ATOM('m', 'i', 'n', 'f') +#define STBL_ATOM QT_ATOM('s', 't', 'b', 'l') +#define STCO_ATOM QT_ATOM('s', 't', 'c', 'o') +#define CO64_ATOM QT_ATOM('c', 'o', '6', '4') + +#define ATOM_PREAMBLE_SIZE 8 +#define COPY_BUFFER_SIZE 33554432 +#define MAX_FTYP_ATOM_SIZE 1048576 + +typedef struct { + uint32_t type; + uint32_t header_size; + uint64_t size; + unsigned char *data; +} atom_t; + +typedef struct { + uint64_t moov_atom_size; + uint64_t stco_offset_count; + uint64_t stco_data_size; + int stco_overflow; + uint32_t depth; +} update_chunk_offsets_context_t; + +typedef struct { + unsigned char *dest; + uint64_t original_moov_size; + uint64_t new_moov_size; +} upgrade_stco_context_t; + +typedef int (*parse_atoms_callback_t)(void *context, atom_t *atom); + +static int parse_atoms( + unsigned char *buf, + uint64_t size, + parse_atoms_callback_t callback, + void *context) +{ + unsigned char *pos = buf; + unsigned char *end = pos + size; + atom_t atom; + int ret; + + while (end - pos >= ATOM_PREAMBLE_SIZE) { + atom.size = BE_32(pos); + atom.type = BE_32(pos + 4); + pos += ATOM_PREAMBLE_SIZE; + atom.header_size = ATOM_PREAMBLE_SIZE; + + switch (atom.size) { + case 1: + if (end - pos < 8) { + fprintf(stderr, "not enough room for 64 bit atom size\n"); + return -1; + } + + atom.size = BE_64(pos); + pos += 8; + atom.header_size = ATOM_PREAMBLE_SIZE + 8; + break; + + case 0: + atom.size = ATOM_PREAMBLE_SIZE + end - pos; + break; + default: + break; + } + + if (atom.size < atom.header_size) { + fprintf(stderr, "atom size %"PRIu64" too small\n", atom.size); + return -1; + } + + atom.size -= atom.header_size; + + if (atom.size > end - pos) { + fprintf(stderr, "atom size %"PRIu64" too big\n", atom.size); + return -1; + } + + atom.data = pos; + ret = callback(context, &atom); + if (ret < 0) { + return ret; + } + + pos += atom.size; + } + + return 0; +} + +static int update_stco_offsets(update_chunk_offsets_context_t *context, atom_t *atom) +{ + uint32_t current_offset; + uint32_t offset_count; + unsigned char *pos; + unsigned char *end; + + printf(" patching stco atom...\n"); + if (atom->size < 8) { + fprintf(stderr, "stco atom size %"PRIu64" too small\n", atom->size); + return -1; + } + + offset_count = BE_32(atom->data + 4); + if (offset_count > (atom->size - 8) / 4) { + fprintf(stderr, "stco offset count %"PRIu32" too big\n", offset_count); + return -1; + } + + context->stco_offset_count += offset_count; + context->stco_data_size += atom->size - 8; + + for (pos = atom->data + 8, end = pos + offset_count * 4; + pos < end; + pos += 4) { + current_offset = BE_32(pos); + if (current_offset > UINT_MAX - context->moov_atom_size) { + context->stco_overflow = 1; + } + current_offset += context->moov_atom_size; + AV_WB32(pos, current_offset); + } + + return 0; +} + +static int update_co64_offsets(update_chunk_offsets_context_t *context, atom_t *atom) +{ + uint64_t current_offset; + uint32_t offset_count; + unsigned char *pos; + unsigned char *end; + + printf(" patching co64 atom...\n"); + if (atom->size < 8) { + fprintf(stderr, "co64 atom size %"PRIu64" too small\n", atom->size); + return -1; + } + + offset_count = BE_32(atom->data + 4); + if (offset_count > (atom->size - 8) / 8) { + fprintf(stderr, "co64 offset count %"PRIu32" too big\n", offset_count); + return -1; + } + + for (pos = atom->data + 8, end = pos + offset_count * 8; + pos < end; + pos += 8) { + current_offset = BE_64(pos); + current_offset += context->moov_atom_size; + AV_WB64(pos, current_offset); + } + + return 0; +} + +static int update_chunk_offsets_callback(void *ctx, atom_t *atom) +{ + update_chunk_offsets_context_t *context = ctx; + int ret; + + switch (atom->type) { + case STCO_ATOM: + return update_stco_offsets(context, atom); + + case CO64_ATOM: + return update_co64_offsets(context, atom); + + case MOOV_ATOM: + case TRAK_ATOM: + case MDIA_ATOM: + case MINF_ATOM: + case STBL_ATOM: + context->depth++; + if (context->depth > 10) { + fprintf(stderr, "atoms too deeply nested\n"); + return -1; + } + + ret = parse_atoms( + atom->data, + atom->size, + update_chunk_offsets_callback, + context); + context->depth--; + return ret; + default: + break; + } + + return 0; +} + +static void set_atom_size(unsigned char *header, uint32_t header_size, uint64_t size) +{ + switch (header_size) { + case 8: + AV_WB32(header, size); + break; + + case 16: + AV_WB64(header + 8, size); + break; + default: + break; + } +} + +static void upgrade_stco_atom(upgrade_stco_context_t *context, atom_t *atom) +{ + unsigned char *pos; + unsigned char *end; + uint64_t new_offset; + uint32_t offset_count; + uint32_t original_offset; + + /* Note: not performing validations since they were performed on the first pass */ + + offset_count = BE_32(atom->data + 4); + + /* write the header */ + memcpy(context->dest, atom->data - atom->header_size, atom->header_size + 8); + AV_WB32(context->dest + 4, CO64_ATOM); + set_atom_size(context->dest, atom->header_size, atom->header_size + 8 + offset_count * 8); + context->dest += atom->header_size + 8; + + /* write the data */ + for (pos = atom->data + 8, end = pos + offset_count * 4; + pos < end; + pos += 4) { + original_offset = BE_32(pos) - context->original_moov_size; + new_offset = (uint64_t)original_offset + context->new_moov_size; + AV_WB64(context->dest, new_offset); + context->dest += 8; + } +} + +static int upgrade_stco_callback(void *ctx, atom_t *atom) +{ + upgrade_stco_context_t *context = ctx; + unsigned char *start_pos; + uint64_t copy_size; + + switch (atom->type) { + case STCO_ATOM: + upgrade_stco_atom(context, atom); + break; + + case MOOV_ATOM: + case TRAK_ATOM: + case MDIA_ATOM: + case MINF_ATOM: + case STBL_ATOM: + /* write the atom header */ + memcpy(context->dest, atom->data - atom->header_size, atom->header_size); + start_pos = context->dest; + context->dest += atom->header_size; + + /* parse internal atoms*/ + if (parse_atoms( + atom->data, + atom->size, + upgrade_stco_callback, + context) < 0) { + return -1; + } + + /* update the atom size */ + set_atom_size(start_pos, atom->header_size, context->dest - start_pos); + break; + + default: + copy_size = atom->header_size + atom->size; + memcpy(context->dest, atom->data - atom->header_size, copy_size); + context->dest += copy_size; + break; + } + + return 0; +} + +static int update_moov_atom( + unsigned char **moov_atom, + uint64_t *moov_atom_size) +{ + update_chunk_offsets_context_t update_context = { 0 }; + upgrade_stco_context_t upgrade_context; + unsigned char *new_moov_atom; + + update_context.moov_atom_size = *moov_atom_size; + + if (parse_atoms( + *moov_atom, + *moov_atom_size, + update_chunk_offsets_callback, + &update_context) < 0) { + return -1; + } + + if (!update_context.stco_overflow) { + return 0; + } + + printf(" upgrading stco atoms to co64...\n"); + upgrade_context.new_moov_size = *moov_atom_size + + update_context.stco_offset_count * 8 - + update_context.stco_data_size; + + new_moov_atom = malloc(upgrade_context.new_moov_size); + if (new_moov_atom == NULL) { + fprintf(stderr, "could not allocate %"PRIu64" bytes for updated moov atom\n", + upgrade_context.new_moov_size); + return -1; + } + + upgrade_context.original_moov_size = *moov_atom_size; + upgrade_context.dest = new_moov_atom; + + if (parse_atoms( + *moov_atom, + *moov_atom_size, + upgrade_stco_callback, + &upgrade_context) < 0) { + free(new_moov_atom); + return -1; + } + + free(*moov_atom); + *moov_atom = new_moov_atom; + *moov_atom_size = upgrade_context.new_moov_size; + + if (upgrade_context.dest != *moov_atom + *moov_atom_size) { + fprintf(stderr, "unexpected - wrong number of moov bytes written\n"); + return -1; + } + + return 0; +} + +FFMPEG_FUNC(jint, fastStart, jstring inputFile, jstring outputFile) { + FILE *infile = NULL; + FILE *outfile = NULL; + unsigned char atom_bytes[ATOM_PREAMBLE_SIZE]; + uint32_t atom_type = 0; + uint64_t atom_size = 0; + uint64_t atom_offset = 0; + int64_t last_offset; + unsigned char *moov_atom = NULL; + unsigned char *ftyp_atom = NULL; + uint64_t moov_atom_size; + uint64_t ftyp_atom_size = 0; + int64_t start_offset = 0; + unsigned char *copy_buffer = NULL; + int bytes_to_copy; + uint64_t free_size = 0; + uint64_t moov_size = 0; + + + printf("Usage: qt-faststart \n" + "Note: alternatively you can use -movflags +faststart in ffmpeg\n"); + + const char *input_file = (*env)->GetStringUTFChars(env, inputFile, JNI_FALSE); + const char *output_file = (*env)->GetStringUTFChars(env, outputFile, JNI_FALSE); + + infile = fopen(input_file, "rb"); + if (!infile) { + perror(input_file); + goto error_out; + } + + /* traverse through the atoms in the file to make sure that 'moov' is + * at the end */ + while (!feof(infile)) { + if (fread(atom_bytes, ATOM_PREAMBLE_SIZE, 1, infile) != 1) { + break; + } + atom_size = BE_32(&atom_bytes[0]); + atom_type = BE_32(&atom_bytes[4]); + + /* keep ftyp atom */ + if (atom_type == FTYP_ATOM) { + if (atom_size > MAX_FTYP_ATOM_SIZE) { + fprintf(stderr, "ftyp atom size %"PRIu64" too big\n", + atom_size); + goto error_out; + } + ftyp_atom_size = atom_size; + free(ftyp_atom); + ftyp_atom = malloc((size_t) ftyp_atom_size); + if (!ftyp_atom) { + fprintf(stderr, "could not allocate %"PRIu64" bytes for ftyp atom\n", + atom_size); + goto error_out; + } + if (fseeko(infile, -ATOM_PREAMBLE_SIZE, SEEK_CUR) || + fread(ftyp_atom, (size_t) atom_size, 1, infile) != 1 || + (start_offset = ftello(infile)) < 0) { + perror(input_file); + goto error_out; + } + } else { + int ret; + /* 64-bit special case */ + if (atom_size == 1) { + if (fread(atom_bytes, ATOM_PREAMBLE_SIZE, 1, infile) != 1) { + break; + } + atom_size = BE_64(&atom_bytes[0]); + ret = fseeko(infile, atom_size - ATOM_PREAMBLE_SIZE * 2, SEEK_CUR); + } else { + ret = fseeko(infile, atom_size - ATOM_PREAMBLE_SIZE, SEEK_CUR); + } + if (ret) { + perror(input_file); + goto error_out; + } + } + printf("%c%c%c%c %10"PRIu64" %"PRIu64"\n", + (atom_type >> 24) & 255, + (atom_type >> 16) & 255, + (atom_type >> 8) & 255, + (atom_type >> 0) & 255, + atom_offset, + atom_size); + if ((atom_type != FREE_ATOM) && + (atom_type != JUNK_ATOM) && + (atom_type != MDAT_ATOM) && + (atom_type != MOOV_ATOM) && + (atom_type != PNOT_ATOM) && + (atom_type != SKIP_ATOM) && + (atom_type != WIDE_ATOM) && + (atom_type != PICT_ATOM) && + (atom_type != UUID_ATOM) && + (atom_type != FTYP_ATOM)) { + fprintf(stderr, "encountered non-QT top-level atom (is this a QuickTime file?)\n"); + break; + } + atom_offset += atom_size; + + /* The atom header is 8 (or 16 bytes), if the atom size (which + * includes these 8 or 16 bytes) is less than that, we won't be + * able to continue scanning sensibly after this atom, so break. */ + if (atom_size < 8) + break; + + if (atom_type == MOOV_ATOM) + moov_size = atom_size; + + if (moov_size && atom_type == FREE_ATOM) { + free_size += atom_size; + atom_type = MOOV_ATOM; + atom_size = moov_size; + } + } + + if (atom_type != MOOV_ATOM) { + printf("last atom in file was not a moov atom\n"); + free(ftyp_atom); + fclose(infile); + return 0; + } + + if (atom_size < 16) { + fprintf(stderr, "bad moov atom size\n"); + goto error_out; + } + + /* moov atom was, in fact, the last atom in the chunk; load the whole + * moov atom */ + if (fseeko(infile, -(atom_size + free_size), SEEK_END)) { + perror(input_file); + goto error_out; + } + last_offset = ftello(infile); + if (last_offset < 0) { + perror(input_file); + goto error_out; + } + moov_atom_size = atom_size; + moov_atom = malloc((size_t) moov_atom_size); + if (!moov_atom) { + fprintf(stderr, "could not allocate %"PRIu64" bytes for moov atom\n", atom_size); + goto error_out; + } + if (fread(moov_atom, (size_t) atom_size, 1, infile) != 1) { + perror(input_file); + goto error_out; + } + + /* this utility does not support compressed atoms yet, so disqualify + * files with compressed QT atoms */ + if (BE_32(&moov_atom[12]) == CMOV_ATOM) { + fprintf(stderr, "this utility does not support compressed moov atoms yet\n"); + goto error_out; + } + + /* close; will be re-opened later */ + fclose(infile); + infile = NULL; + + if (update_moov_atom(&moov_atom, &moov_atom_size) < 0) { + goto error_out; + } + + /* re-open the input file and open the output file */ + infile = fopen(input_file, "rb"); + if (!infile) { + perror(input_file); + goto error_out; + } + + if (start_offset > 0) { /* seek after ftyp atom */ + if (fseeko(infile, start_offset, SEEK_SET)) { + perror(input_file); + goto error_out; + } + + last_offset -= start_offset; + } + + outfile = fopen(output_file, "wb"); + if (!outfile) { + perror(output_file); + goto error_out; + } + + /* dump the same ftyp atom */ + if (ftyp_atom_size > 0) { + printf(" writing ftyp atom...\n"); + if (fwrite(ftyp_atom, (size_t) ftyp_atom_size, 1, outfile) != 1) { + perror(output_file); + goto error_out; + } + } + + /* dump the new moov atom */ + printf(" writing moov atom...\n"); + if (fwrite(moov_atom, (size_t) moov_atom_size, 1, outfile) != 1) { + perror(output_file); + goto error_out; + } + + /* copy the remainder of the infile, from offset 0 -> last_offset - 1 */ + bytes_to_copy = (int) MIN(COPY_BUFFER_SIZE, last_offset); + copy_buffer = malloc(bytes_to_copy); + if (!copy_buffer) { + fprintf(stderr, "could not allocate %d bytes for copy_buffer\n", bytes_to_copy); + goto error_out; + } + printf(" copying rest of file...\n"); + while (last_offset) { + bytes_to_copy = (int) MIN(bytes_to_copy, last_offset); + + if (fread(copy_buffer, (size_t) bytes_to_copy, 1, infile) != 1) { + perror(input_file); + goto error_out; + } + if (fwrite(copy_buffer, (size_t) bytes_to_copy, 1, outfile) != 1) { + perror(output_file); + goto error_out; + } + last_offset -= bytes_to_copy; + } + + fclose(infile); + fclose(outfile); + free(moov_atom); + free(ftyp_atom); + free(copy_buffer); + (*env)->ReleaseStringUTFChars(env, inputFile, input_file); + (*env)->ReleaseStringUTFChars(env, outputFile, output_file); + + return 0; + +error_out: + if (infile) + fclose(infile); + if (outfile) + fclose(outfile); + free(moov_atom); + free(ftyp_atom); + free(copy_buffer); + return 1; +} diff --git a/app/src/main/java/com/frank/ffmpeg/FFmpegCmd.java b/app/src/main/java/com/frank/ffmpeg/FFmpegCmd.java index a113ab7..35649d1 100644 --- a/app/src/main/java/com/frank/ffmpeg/FFmpegCmd.java +++ b/app/src/main/java/com/frank/ffmpeg/FFmpegCmd.java @@ -1,5 +1,7 @@ package com.frank.ffmpeg; +import android.text.TextUtils; + import com.frank.ffmpeg.listener.OnHandleListener; public class FFmpegCmd { @@ -24,6 +26,22 @@ public class FFmpegCmd { } }).start(); } + + /** + * 使用FastStart把Moov移动到Mdat前面 + * @param inputFile inputFile + * @param outputFile outputFile + * @return 是否操作成功 + */ + public int moveMoovAhead(String inputFile, String outputFile) { + if (TextUtils.isEmpty(inputFile) || TextUtils.isEmpty(outputFile)) { + return -1; + } + return fastStart(inputFile, outputFile); + } + private native static int handle(String[] commands); + private native static int fastStart(String inputFile, String outputFile); + } \ No newline at end of file diff --git a/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java index 74cca13..902b565 100644 --- a/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java +++ b/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java @@ -5,14 +5,18 @@ import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; +import android.util.Log; import android.view.View; import android.widget.LinearLayout; import android.widget.ProgressBar; + +import com.frank.ffmpeg.FFmpegCmd; import com.frank.ffmpeg.R; import com.frank.ffmpeg.format.VideoLayout; import com.frank.ffmpeg.handler.FFmpegHandler; import com.frank.ffmpeg.util.FFmpegUtil; import com.frank.ffmpeg.util.FileUtil; + import java.io.File; import static com.frank.ffmpeg.handler.FFmpegHandler.MSG_BEGIN; @@ -20,19 +24,21 @@ import static com.frank.ffmpeg.handler.FFmpegHandler.MSG_FINISH; public class VideoHandleActivity extends BaseActivity { + private final static String TAG = VideoHandleActivity.class.getSimpleName(); private static final String PATH = Environment.getExternalStorageDirectory().getPath(); private ProgressBar progressVideo; private LinearLayout layoutVideoHandle; private int viewId; private FFmpegHandler ffmpegHandler; + private final static boolean useFFmpegCmd = true; @SuppressLint("HandlerLeak") - private Handler mHandler = new Handler(){ + private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); - switch (msg.what){ + switch (msg.what) { case MSG_BEGIN: progressVideo.setVisibility(View.VISIBLE); layoutVideoHandle.setVisibility(View.GONE); @@ -77,7 +83,8 @@ public class VideoHandleActivity extends BaseActivity { R.id.btn_reverse_video, R.id.btn_denoise_video, R.id.btn_to_image, - R.id.btn_pip + R.id.btn_pip, + R.id.btn_moov ); } @@ -94,6 +101,7 @@ public class VideoHandleActivity extends BaseActivity { /** * 调用ffmpeg处理视频 + * * @param srcFile srcFile */ private void doHandleVideo(String srcFile) { @@ -105,7 +113,7 @@ public class VideoHandleActivity extends BaseActivity { showToast(getString(R.string.wrong_video_format)); return; } - switch (viewId){ + switch (viewId) { case R.id.btn_video_transform://视频转码:mp4转flv、wmv, 或者flv、wmv转Mp4 String transformVideo = PATH + File.separator + "transformVideo.flv"; commandLine = FFmpegUtil.transformVideo(srcFile, transformVideo); @@ -177,7 +185,7 @@ public class VideoHandleActivity extends BaseActivity { case R.id.btn_combine_video://图片合成视频 //图片所在路径,图片命名格式img+number.jpg String picturePath = PATH + File.separator + "img/"; - if (!FileUtil.checkFileExist(picturePath)){ + if (!FileUtil.checkFileExist(picturePath)) { return; } String combineVideo = PATH + File.separator + "combineVideo.mp4"; @@ -187,7 +195,7 @@ public class VideoHandleActivity extends BaseActivity { String input1 = PATH + File.separator + "input1.mp4"; String input2 = PATH + File.separator + "input2.mp4"; String outputFile = PATH + File.separator + "multi.mp4"; - if (!FileUtil.checkFileExist(input1) || !FileUtil.checkFileExist(input2)){ + if (!FileUtil.checkFileExist(input1) || !FileUtil.checkFileExist(input2)) { return; } commandLine = FFmpegUtil.multiVideo(input1, input2, outputFile, VideoLayout.LAYOUT_HORIZONTAL); @@ -203,9 +211,9 @@ public class VideoHandleActivity extends BaseActivity { case R.id.btn_to_image://视频转图片 String imagePath = PATH + File.separator + "Video2Image/";//图片保存路径 File imageFile = new File(imagePath); - if (!imageFile.exists()){ + if (!imageFile.exists()) { boolean result = imageFile.mkdir(); - if (!result){ + if (!result) { return; } } @@ -217,7 +225,7 @@ public class VideoHandleActivity extends BaseActivity { case R.id.btn_pip://两个视频合成画中画 String inputFile1 = PATH + File.separator + "beyond.mp4"; String inputFile2 = PATH + File.separator + "small_girl.mp4"; - if (!FileUtil.checkFileExist(inputFile1) && !FileUtil.checkFileExist(inputFile2)){ + if (!FileUtil.checkFileExist(inputFile1) && !FileUtil.checkFileExist(inputFile2)) { return; } //x、y坐标点需要根据全屏视频与小视频大小,进行计算 @@ -227,6 +235,26 @@ public class VideoHandleActivity extends BaseActivity { String picInPic = PATH + File.separator + "PicInPic.mp4"; commandLine = FFmpegUtil.picInPicVideo(inputFile1, inputFile2, x, y, picInPic); break; + case R.id.btn_moov://moov前移操作,针对mp4视频moov在mdat后面的情况 + if (!srcFile.endsWith(FileUtil.TYPE_MP4)) { + showToast(getString(R.string.tip_not_mp4_video)); + return; + } + String filePath = FileUtil.getFilePath(srcFile); + String fileName = FileUtil.getFileName(srcFile); + Log.e(TAG, "moov filePath=" + filePath + "--fileName=" + fileName); + fileName = "moov_" + fileName; + String moovPath = filePath + File.separator + fileName; + if (useFFmpegCmd) { + commandLine = FFmpegUtil.moveMoovAhead(srcFile, moovPath); + } else { + long start = System.currentTimeMillis(); + FFmpegCmd ffmpegCmd = new FFmpegCmd(); + int result = ffmpegCmd.moveMoovAhead(srcFile, moovPath); + Log.e(TAG, "result=" + (result == 0)); + Log.e(TAG, "move moov use time=" + (System.currentTimeMillis() - start)); + } + break; default: break; } diff --git a/app/src/main/java/com/frank/ffmpeg/util/FFmpegUtil.java b/app/src/main/java/com/frank/ffmpeg/util/FFmpegUtil.java index 9103ddc..17dc8b8 100644 --- a/app/src/main/java/com/frank/ffmpeg/util/FFmpegUtil.java +++ b/app/src/main/java/com/frank/ffmpeg/util/FFmpegUtil.java @@ -321,4 +321,16 @@ public class FFmpegUtil { return reverseVideo.split(" "); } + /** + * mp4视频的moov往mdat前面移动 + * @param inputFile inputFile + * @param outputFile outputFile + * @return 移动moov命令行 + */ + public static String[] moveMoovAhead(String inputFile, String outputFile){ + String moovCmd = "ffmpeg -i %s -movflags faststart -acodec copy -vcodec copy %s"; + moovCmd = String.format(Locale.CHINESE, moovCmd, inputFile, outputFile); + return moovCmd.split(" "); + } + } diff --git a/app/src/main/java/com/frank/ffmpeg/util/FileUtil.java b/app/src/main/java/com/frank/ffmpeg/util/FileUtil.java index f1997a0..01a7cde 100644 --- a/app/src/main/java/com/frank/ffmpeg/util/FileUtil.java +++ b/app/src/main/java/com/frank/ffmpeg/util/FileUtil.java @@ -26,7 +26,7 @@ public class FileUtil { private final static String TYPE_OGG = "ogg"; private final static String TYPE_AC3 = "ac3"; - private final static String TYPE_MP4 = "mp4"; + public final static String TYPE_MP4 = "mp4"; private final static String TYPE_MKV = "mkv"; private final static String TYPE_WEBM = "webm"; private final static String TYPE_AVI = "avi"; @@ -36,6 +36,7 @@ public class FileUtil { private final static String TYPE_M3U8 = "m3u8"; private final static String TYPE_3GP = "3gp"; private final static String TYPE_MOV = "mov"; + private final static String TYPE_MPG = "mpg"; public static boolean concatFile(String srcFilePath, String appendFilePath, String concatFilePath){ if(TextUtils.isEmpty(srcFilePath) @@ -134,7 +135,8 @@ public class FileUtil { || path.endsWith(TYPE_3GP) || path.endsWith(TYPE_TS) || path.endsWith(TYPE_M3U8) - || path.endsWith(TYPE_MOV); + || path.endsWith(TYPE_MOV) + || path.endsWith(TYPE_MPG); } public static String getFileSuffix(String fileName) { @@ -144,4 +146,18 @@ public class FileUtil { return fileName.substring(fileName.lastIndexOf(".")); } + public static String getFilePath(String filePath) { + if (TextUtils.isEmpty(filePath) || !filePath.contains("/")) { + return null; + } + return filePath.substring(0, filePath.lastIndexOf("/")); + } + + public static String getFileName(String filePath) { + if (TextUtils.isEmpty(filePath) || !filePath.contains("/")) { + return null; + } + return filePath.substring(filePath.lastIndexOf("/") + 1); + } + } diff --git a/app/src/main/res/layout/activity_video_handle.xml b/app/src/main/res/layout/activity_video_handle.xml index 35dcccd..fb36e97 100644 --- a/app/src/main/res/layout/activity_video_handle.xml +++ b/app/src/main/res/layout/activity_video_handle.xml @@ -116,6 +116,13 @@ android:layout_marginTop="4dp" android:text="@string/video_pip"/> +