两种方式移动moov:FFmpeg命令行和fast_start代码

两种方式移动moov:FFmpeg命令行和fast_start代码
pull/107/head
xufulong 5 years ago
parent dca13944d5
commit 5dd3f0eb8a
  1. 1
      app/CMakeLists.txt
  2. 660
      app/src/main/cpp/fast_start.c
  3. 18
      app/src/main/java/com/frank/ffmpeg/FFmpegCmd.java
  4. 30
      app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java
  5. 12
      app/src/main/java/com/frank/ffmpeg/util/FFmpegUtil.java
  6. 20
      app/src/main/java/com/frank/ffmpeg/util/FileUtil.java
  7. 7
      app/src/main/res/layout/activity_video_handle.xml
  8. 2
      app/src/main/res/values/strings.xml

@ -31,6 +31,7 @@ add_library( # Sets the name of the library.
src/main/cpp/media_player.c src/main/cpp/media_player.c
src/main/cpp/video_filter.c src/main/cpp/video_filter.c
src/main/cpp/audio_lame.c src/main/cpp/audio_lame.c
src/main/cpp/fast_start.c
) )
add_library( ffmpeg add_library( ffmpeg

@ -0,0 +1,660 @@
//
// Created by frank on 2019-12-06.
//
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <limits.h>
#include <jni.h>
#include <ffmpeg_jni_define.h>
#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 <infile.mp4> <outfile.mp4>\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;
}

@ -1,5 +1,7 @@
package com.frank.ffmpeg; package com.frank.ffmpeg;
import android.text.TextUtils;
import com.frank.ffmpeg.listener.OnHandleListener; import com.frank.ffmpeg.listener.OnHandleListener;
public class FFmpegCmd { public class FFmpegCmd {
@ -24,6 +26,22 @@ public class FFmpegCmd {
} }
}).start(); }).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 handle(String[] commands);
private native static int fastStart(String inputFile, String outputFile);
} }

@ -5,14 +5,18 @@ import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import com.frank.ffmpeg.FFmpegCmd;
import com.frank.ffmpeg.R; import com.frank.ffmpeg.R;
import com.frank.ffmpeg.format.VideoLayout; import com.frank.ffmpeg.format.VideoLayout;
import com.frank.ffmpeg.handler.FFmpegHandler; import com.frank.ffmpeg.handler.FFmpegHandler;
import com.frank.ffmpeg.util.FFmpegUtil; import com.frank.ffmpeg.util.FFmpegUtil;
import com.frank.ffmpeg.util.FileUtil; import com.frank.ffmpeg.util.FileUtil;
import java.io.File; import java.io.File;
import static com.frank.ffmpeg.handler.FFmpegHandler.MSG_BEGIN; import static com.frank.ffmpeg.handler.FFmpegHandler.MSG_BEGIN;
@ -20,12 +24,14 @@ import static com.frank.ffmpeg.handler.FFmpegHandler.MSG_FINISH;
public class VideoHandleActivity extends BaseActivity { public class VideoHandleActivity extends BaseActivity {
private final static String TAG = VideoHandleActivity.class.getSimpleName();
private static final String PATH = Environment.getExternalStorageDirectory().getPath(); private static final String PATH = Environment.getExternalStorageDirectory().getPath();
private ProgressBar progressVideo; private ProgressBar progressVideo;
private LinearLayout layoutVideoHandle; private LinearLayout layoutVideoHandle;
private int viewId; private int viewId;
private FFmpegHandler ffmpegHandler; private FFmpegHandler ffmpegHandler;
private final static boolean useFFmpegCmd = true;
@SuppressLint("HandlerLeak") @SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() { private Handler mHandler = new Handler() {
@ -77,7 +83,8 @@ public class VideoHandleActivity extends BaseActivity {
R.id.btn_reverse_video, R.id.btn_reverse_video,
R.id.btn_denoise_video, R.id.btn_denoise_video,
R.id.btn_to_image, 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处理视频 * 调用ffmpeg处理视频
*
* @param srcFile srcFile * @param srcFile srcFile
*/ */
private void doHandleVideo(String srcFile) { private void doHandleVideo(String srcFile) {
@ -227,6 +235,26 @@ public class VideoHandleActivity extends BaseActivity {
String picInPic = PATH + File.separator + "PicInPic.mp4"; String picInPic = PATH + File.separator + "PicInPic.mp4";
commandLine = FFmpegUtil.picInPicVideo(inputFile1, inputFile2, x, y, picInPic); commandLine = FFmpegUtil.picInPicVideo(inputFile1, inputFile2, x, y, picInPic);
break; 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: default:
break; break;
} }

@ -321,4 +321,16 @@ public class FFmpegUtil {
return reverseVideo.split(" "); 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(" ");
}
} }

@ -26,7 +26,7 @@ public class FileUtil {
private final static String TYPE_OGG = "ogg"; private final static String TYPE_OGG = "ogg";
private final static String TYPE_AC3 = "ac3"; 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_MKV = "mkv";
private final static String TYPE_WEBM = "webm"; private final static String TYPE_WEBM = "webm";
private final static String TYPE_AVI = "avi"; 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_M3U8 = "m3u8";
private final static String TYPE_3GP = "3gp"; private final static String TYPE_3GP = "3gp";
private final static String TYPE_MOV = "mov"; private final static String TYPE_MOV = "mov";
private final static String TYPE_MPG = "mpg";
public static boolean concatFile(String srcFilePath, String appendFilePath, String concatFilePath){ public static boolean concatFile(String srcFilePath, String appendFilePath, String concatFilePath){
if(TextUtils.isEmpty(srcFilePath) if(TextUtils.isEmpty(srcFilePath)
@ -134,7 +135,8 @@ public class FileUtil {
|| path.endsWith(TYPE_3GP) || path.endsWith(TYPE_3GP)
|| path.endsWith(TYPE_TS) || path.endsWith(TYPE_TS)
|| path.endsWith(TYPE_M3U8) || path.endsWith(TYPE_M3U8)
|| path.endsWith(TYPE_MOV); || path.endsWith(TYPE_MOV)
|| path.endsWith(TYPE_MPG);
} }
public static String getFileSuffix(String fileName) { public static String getFileSuffix(String fileName) {
@ -144,4 +146,18 @@ public class FileUtil {
return fileName.substring(fileName.lastIndexOf(".")); 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);
}
} }

@ -116,6 +116,13 @@
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="@string/video_pip"/> android:text="@string/video_pip"/>
<Button
android:id="@+id/btn_moov"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/video_moov"/>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

@ -40,6 +40,7 @@
<string name="video_image">视频转图片</string> <string name="video_image">视频转图片</string>
<string name="video_pip">视频画中画</string> <string name="video_pip">视频画中画</string>
<string name="video_preview">播放预览</string> <string name="video_preview">播放预览</string>
<string name="video_moov">MOOV前移</string>
<string name="swap">切换</string> <string name="swap">切换</string>
<string name="start">开始</string> <string name="start">开始</string>
@ -69,5 +70,6 @@
<string name="wrong_audio_format">非音频文件</string> <string name="wrong_audio_format">非音频文件</string>
<string name="wrong_video_format">非视频文件</string> <string name="wrong_video_format">非视频文件</string>
<string name="converting_video">正在反转视频&#8230;</string> <string name="converting_video">正在反转视频&#8230;</string>
<string name="tip_not_mp4_video">该视频不是mp4,无法进行moov前移操作</string>
</resources> </resources>

Loading…
Cancel
Save