虽然Android4.1之后版本通过MediaPlayer的setNextMediaPlayer
()方法视频无缝播放,但是国产的N多ARM芯片并不支持这一特性,
如何才能让现有的平台达到无缝播放效果,方法有N多种,比如通过http的ts流方式或者将多个文件合并成一个文件在去播放,就可以变相解决该问题。
经过测试可以把同种编码格式的mpg文件cat在一起播放以达到目的,笔者顺着这个思路逐步从Linux kernel层到Android JNI和App层
逐步来实现;
java播放层
1、设置要播放的文件总大小
2、设置播放序列
3、将虚拟的proc内存文件赋值给播放器
int fd = MpgFS.open("/dev/mpg_fs");
int fileSize = 8290340 + 16240676 + 16240676 + 16240676;
MpgFS.setFileSize(fd, fileSize);
List list = new ArrayList();
list.add(new PlayNode("/mnt/sdcard/576/FM0.mpg", 8290340, 0));
list.add(new PlayNode("/mnt/sdcard/576/FM1.mpg", 16240676, 1));
list.add(new PlayNode("/mnt/sdcard/576/FM2.mpg", 16240676, 2));
list.add(new PlayNode("/mnt/sdcard/576/FM3.mpg", 16240676, 3));
MpgFS.setPlayList(list, fd);
play.setVideoPath("/proc/mpg_fs");
JNI层的java部分
import java.util.List;
public class MpgFS {
static {
System.loadLibrary("mpgfs_jni");
}
public static final int NODE_SIZE = 128;
public native static int open(String device);
public native static int close(int fd);
private native static int ioctl(int fd, int cmd, byte[] buf, long len);
public static void int2buf(int value, byte[] buf, int offset) {
buf[offset + 0] = (byte) ((value >> 0) & 0xff);
buf[offset + 1] = (byte) ((value >> 8) & 0xff);
buf[offset + 2] = (byte) ((value >> 16) & 0xff);
buf[offset + 3] = (byte) ((value >> 24) & 0xff);
}
public static int setFileSize(int fd, long size) {
return ioctl(fd, 5, null, size);
}
public static int setPlayList(List list, int fd) {
if (list == null || list.size() == 0) {
return -1;
}
int size = list.size();
byte[] buffer = new byte[NODE_SIZE * size];
for (int i = 0; i < size; i++) {
int offset = i * NODE_SIZE;
PlayNode node = list.get(i);
int2buf(node.size, buffer, offset);
int2buf(node.seq, buffer, offset+4);
int2buf(node.fd, buffer, offset+8);
System.arraycopy(node.name.getBytes(), 0, buffer, offset+12, node.name.length());
}
return ioctl(fd, 10000 + size, buffer, buffer.length);
}
}
JNI层的C部分
#include
#include
#include
#include
#include
#include
#include
#include
#define LOG_TAG "MpgFS_JNI"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
//#define LOGD(...)
typedef struct play_node {
int size;
int seq;
int fd;
char name[128-12];
} play_node_t;
static const char *kClassName = "com/signway/mpgfs/MpgFS";
jint openNative(JNIEnv *env, jobject obj, jstring name);
jint closeNative(JNIEnv *env, jobject obj, jint fd);
jint ioctlNative(JNIEnv *env, jobject obj, jint fd, jint cmd, jbyteArray buf, jlong length);
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{"open", "(Ljava/lang/String;)I", (void*)openNative},
{"close", "(I)I", (void*)closeNative},
{"ioctl", "(II[BJ)I", (void*)ioctlNative}
};
jint openNative(JNIEnv *env, jobject obj, jstring name)
{
int fd = -1;
const char *device = (*env)->GetStringUTFChars(env, name, NULL);
fd = open(device, O_RDONLY);
LOGD("device is %s and fd is %d", device, fd);
(*env)->ReleaseStringUTFChars(env, name, device);
return fd;
}
jint closeNative(JNIEnv *env, jobject obj, jint fd)
{
LOGD("close device");
close(fd);
}
jint ioctlNative(JNIEnv *env, jobject obj, jint fd, jint cmd, jbyteArray buf, jlong length)
{
int ret = 0;
play_node_t play_file;
memset(&play_file, 0, sizeof(play_node_t));
LOGD("ioctrl, cmd is:%d", cmd);
switch (cmd) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5: // set file length
ret = ioctl(fd, cmd, &length);
break;
default:
if (cmd > 10000) {
char *buffer = malloc(length);
if (buffer) {
(*env)->GetByteArrayRegion(env, buf, 0, (jsize)length, (jbyte*)buffer);
ret = ioctl(fd, cmd, buffer);
free(buffer);
buffer = NULL;
break;
}
LOGE("malloc error for buffer length is: %d", length);
ret = -1;
}
break;
}
return ret;
}
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv *env = NULL;
jclass cls;
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("current JNI not support JNI_VERSION_1_4");
return JNI_ERR;
}
cls = (*env)->FindClass(env, kClassName);
if (cls == NULL) {
LOGE("can not find class %s", kClassName);
return JNI_ERR;
}
if ((*env)->RegisterNatives(env, cls, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) != JNI_OK) {
LOGE("can not register native methods");
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
kernel层的驱动部分
1、实现一个驱动/dev/mpg_fs,用于java层来控制播放列表
2、使用一个内存文件,/proc/mpg_fs,用来给mediaplayer作为播放文件路径
3、在kernel里读取文件,送往内存文件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct proc_dir_entry* entry = NULL;
static loff_t g_size = 0;
static struct file *fd = NULL;
static unsigned char buffer[16*1024];
typedef struct play_node {
int size;
int seq;
int fd;
char name[128-12];
} play_node_t;
static play_node_t *play_list = NULL;
static int play_list_count = 0;
static DEFINE_MUTEX(mpg_fs_mutex);
#define MUTEX_LOCK() mutex_lock(&mpg_fs_mutex)
#define MUTEX_UNLOCK() mutex_unlock(&mpg_fs_mutex)
#define DEVICE_NAME "mpg_fs"
static int MPG_DEV_Major = 0;
static struct class *mpg_dev_class;
static int mpg_fs_open(struct inode *inode, struct file *file) {
printk("mpg_fs_open()\n");
entry->size = g_size;
return 0;
}
static int mpg_fs_release(struct inode *inode, struct file *file) {
printk("mpg_fs_release()\n");
int i = 0;
set_fs(KERNEL_DS);
for (i = 0; i < play_list_count; i++) {
filp_close(play_list[i].fd, NULL);
}
kfree(play_list);
play_list = NULL;
play_list_count = 0;
return 0;
}
static int mpg_fs_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) {
int i = 0;
int len = 0;
int left = 0;
int size = 0;
loff_t pos = *f_pos;
loff_t sum = 0;
set_fs(KERNEL_DS);
for (i = 0; i < play_list_count; i++) {
size = play_list[i].size;
if (pos + count <= sum + size) {
fd = play_list[i].fd;
fd->f_op->llseek(fd, pos-sum, 0);
len = fd->f_op->read(fd, buffer, count, &fd->f_pos);
break;
}
if (pos < sum + size) {
left = sum + size - pos;
play_list[i].fd;
fd->f_op->llseek(fd, pos-sum, 0);
fd->f_op->read(fd, buffer, left, &fd->f_pos);
fd = play_list[i+1].fd;
fd->f_op->llseek(fd, 0, 0);
fd->f_op->read(fd, buffer+left, count-left, &fd->f_pos);
len = count;
break;
}
sum += size;
}
if (len > 0) {
*f_pos += len;
copy_to_user(buf, buffer, len);
} else if (len == 0) {
//TODO::
}
return len;
}
static loff_t mpg_fs_llseek(struct file *file, loff_t f_pos, int orig) {
if (orig == 0) {
file->f_pos = f_pos;
} else if (orig == 1) {
file->f_pos += f_pos;
} else if (orig == 2) {
file->f_pos = g_size;
}
return file->f_pos;
}
static struct file_operations mpg_fs_fops = {
.owner = THIS_MODULE,
.open = mpg_fs_open,
.release = mpg_fs_release,
.read = mpg_fs_read,
.llseek = mpg_fs_llseek,
};
static int mpg_dev_open(struct inode *inode, struct file *file) {
printk("mpg_dev_open()\n");
return 0;
}
static int mpg_dev_release(struct inode *inode, struct file *file) {
printk("mpg_dev_release()\n");
return 0;
}
static long mpg_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
int ret = 0;
printk("mpg_dev_ioctl(), cmd: %d\n", cmd);
MUTEX_LOCK();
switch (cmd) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5: // set file size
copy_from_user(&g_size, arg, sizeof(loff_t));
entry->size = g_size;
printk("set file size: %lld\n", g_size);
break;
default:
if (cmd > 10000) {
int i = 0;
play_list_count = cmd - 10000;
play_list = kzalloc(play_list_count * sizeof(play_node_t), GFP_KERNEL);
copy_from_user(play_list, arg, play_list_count * sizeof(play_node_t));
for (i = 0; i < play_list_count; i++) {
play_list[i].fd = filp_open(play_list[i].name, O_RDONLY, 0666);
printk("file name:%s size:%d seq:%d\n", play_list[i].name, play_list[i].size, play_list[i].seq);
}
}
break;
}
MUTEX_UNLOCK();
return ret;
}
static struct file_operations mpg_dev_fops = {
.owner = THIS_MODULE,
.open = mpg_dev_open,
.release = mpg_dev_release,
.unlocked_ioctl = mpg_dev_ioctl
};
static int __init mpg_init(void) {
printk("\nMPG FS DRIVER MODULE INIT\n");
entry = proc_create("mpg_fs", S_IRUGO, NULL, &mpg_fs_fops);
MPG_DEV_Major = register_chrdev(0, DEVICE_NAME, &mpg_dev_fops);
mpg_dev_class = class_create(THIS_MODULE, DEVICE_NAME);
device_create(mpg_dev_class, NULL, MKDEV(MPG_DEV_Major, 0), NULL, DEVICE_NAME);
return 0;
}
static void __exit mpg_exit(void) {
printk("\nMPG FS DRIVER MODULE EXIT\n");
remove_proc_entry("mpg_fs", NULL);
unregister_chrdev(MPG_DEV_Major, DEVICE_NAME);
device_destroy(mpg_dev_class, MKDEV(MPG_DEV_Major, 0));
class_destroy(mpg_dev_class);
}
module_init(mpg_init);
module_exit(mpg_exit);
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("pmg-ps-fs");
MODULE_LICENSE("GPL");