前段时间花半个月左右实现Android平台上播放TCP传输的数据流,今天就做一个统一整理
需求:在android实现播放通过TCP传输过来的TS数据流,数据流不包含同步信息,只包含图像不需要处理声音信息(如需要处理同步,程序的复杂度至少翻倍)
实现:前前后后大概用了三种方式,下面逐一介绍
方案一:文件+JAVA MediaPlayer
流程 :先将TCP传输的数据保存到文件中,然后通过Android上层MediaPlayer播放文件。这个方案技术实现较简单。不详述
缺点: 需要频繁的切换播放文件,切换期间会黑屏,而且存在较严重延迟。
方案二与方案三在接收数据上保持一致,使用C编写,在应用的目录创建命名管道文件,JAVA通过JNI进行调用。下面主要介绍解码流程
方案二:管道 + FFMPEG
流程:ffmpeg的编译参考
附上我在使用的编译脚本
-
#!/bin/bash
-
######################################################
-
# Usage:
-
# put this script in top of FFmpeg source tree
-
# ./build_android
-
# It generates binary for following architectures:
-
# ARMv6
-
# ARMv6+VFP
-
# ARMv7+VFPv3-d16 (Tegra2)
-
# ARMv7+Neon (Cortex-A8)
-
# Customizing:
-
# 1. Feel free to change ./configure parameters for more features
-
# 2. To adapt other ARM variants
-
# set $CPU and $OPTIMIZE_CFLAGS
-
# call build_one
-
######################################################
-
NDK=/tool/android-ndk-r8e
-
PLATFORM=$NDK/platforms/android-8/arch-arm/
-
PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86_64
-
function build_one
-
{
-
./configure --target-os=linux
-
--prefix=$PREFIX
-
--enable-cross-compile
-
--extra-libs="-lgcc"
-
--arch=arm
-
--cc=$PREBUILT/bin/arm-linux-androideabi-gcc
-
--cross-prefix=$PREBUILT/bin/arm-linux-androideabi-
-
--nm=$PREBUILT/bin/arm-linux-androideabi-nm
-
--sysroot=$PLATFORM
-
--extra-cflags=" -O3 -fpic -DANDROID -DHAVE_SYS_UIO_H=1 -Dipv6mr_interface=ipv6mr_ifindex -fasm -Wno-psabi -fno-short-enums -fno-strict-aliasing -finline-limit=300 $OPTIMIZE_CFLAGS "
-
--disable-shared
-
--enable-static
-
--extra-ldflags="-Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib -lc -lm -ldl -llog"
-
--disable-everything
-
--enable-demuxer=mov
-
--enable-demuxer=h264
-
--enable-demuxer=mpegts
-
--disable-ffplay
-
--enable-protocol=file
-
--enable-avformat
-
--enable-avcodec
-
--enable-decoder=rawvideo
-
--enable-decoder=mjpeg
-
--enable-decoder=h263
-
--enable-decoder=mpeg4
-
--enable-decoder=h264
-
--enable-parser=h264
-
--disable-network
-
--enable-zlib
-
--disable-avfilter
-
--disable-ffprobe
-
--disable-avdevice
-
$ADDITIONAL_CONFIGURE_FLAG
-
-
make clean
-
make -j4 install
-
$PREBUILT/bin/arm-linux-androideabi-ar d libavcodec/libavcodec.a inverse.o
-
$PREBUILT/bin/arm-linux-androideabi-ld -rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -soname libffmpeg.so -shared -nostdlib -z,noexecstack -Bsymbolic --whole-archive --no-undefined -o $PREFIX/libffmpeg.so libavcodec/libavcodec.a libavformat/libavformat.a libavutil/libavutil.a libswscale/libswscale.a -lc -lm -lz -ldl -llog --warn-once --dynamic-linker=/system/bin/linker $PREBUILT/lib/gcc/arm-linux-androideabi/4.4.3/libgcc.a
-
}
-
-
#arm v6
-
#CPU=armv6
-
#OPTIMIZE_CFLAGS="-marm -march=$CPU"
-
#PREFIX=./android/$CPU
-
#ADDITIONAL_CONFIGURE_FLAG=
-
#build_one
-
-
#arm v7vfpv3
-
#CPU=armv7-a
-
#OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfpv3-d16 -marm -march=$CPU "
-
#PREFIX=./android/$CPU
-
#ADDITIONAL_CONFIGURE_FLAG=
-
#build_one
-
-
#arm v7vfp
-
#CPU=armv7-a
-
#OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
-
#PREFIX=./android/$CPU-vfp
-
#ADDITIONAL_CONFIGURE_FLAG=
-
#build_one
-
-
#arm v7n
-
CPU=armv7-a
-
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=neon -marm -march=$CPU -mtune=cortex-a8"
-
PREFIX=./android/$CPU
-
ADDITIONAL_CONFIGURE_FLAG=--enable-neon
-
build_one
-
-
#arm v6+vfp
-
#CPU=armv6
-
#OPTIMIZE_CFLAGS="-DCMP_HAVE_VFP -mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU"
-
#PREFIX=./android/${CPU}_vfp
-
#ADDITIONAL_CONFIGURE_FLAG=
-
#build_one
之前使用相同脚本,在最后链接成ffmpeg.so时出现错误,反复折腾,最后发现问题出现在代码上,使用下载的代码包,无法正常链接出SO,使用GIT CLONE然后checkout tag,顺利编译出ffmpeg.so
然后使用ffmpeg的API编写解码流程,这个网上资料较多不详述。主要问题出现在如何显示上,最开始的方案如下
-
static android::sp<android::Surface> native_surface;
-
-
static android::Surface* getNativeSurface(JNIEnv* env, jobject jsurface, jint version)
-
{
-
jclass clazz = env->FindClass("android/view/Surface");
-
jfieldID field_surface;
-
if(version <=8)
-
{
-
field_surface = env->GetFieldID(clazz, "mSurface", "I");
-
}
-
else
-
field_surface = env->GetFieldID(clazz, ANDROID_VIEW_SURFACE_JNI_ID, "I");
-
-
if (field_surface == NULL)
-
{
-
return NULL;
-
}
-
return (android::Surface *) env->GetIntField(jsurface, field_surface);
-
}
-
-
int setSurface(JNIEnv *env, jobject jsurface, jint version)
-
{
-
native_surface = getNativeSurface(env, jsurface, version);
-
-
if(android::Surface::isValid(native_surface))
-
{
-
__android_log_print(ANDROID_LOG_INFO, "libjni", "native_surface is valid");
-
return 1;
-
}
-
else
-
__android_log_print(ANDROID_LOG_ERROR, "libjni", "native_surface is invalid");
-
-
return 0;
-
}
通过这个获得显示缓冲,解码转成RGB后直接写缓冲
-
sws_scale(img_convert_ctx,
pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
picture.data, picture.linesize)
-
native_surface->lock(&info,&dirtyRegion,true);
-
memcpy(info.bits,picture.data[0],(picture.linesize[0])*720);
-
native_surface->unlockAndPost();
这种方式获得的缓冲在高于4.0.3的系统上无法正常运行,且编译时需要引入android源代码库,操作繁琐且不具有通用性。
后来换成NDK提供的方式
-
static ANativeWindow* theNativeWindow;
ANativeWindow_Buffer wbuffer;
-
JNIEXPORT void JNICALL Java_com_inesa_jni_DisVideo_setNativeSurface
-
(JNIEnv *env, jobject obj, jobject jsurface){
-
//setSurface(env, jsurface, 16);
-
theNativeWindow = ANativeWindow_fromSurface(env, jsurface);
-
}
然后就可以在
theNativeWindow上刷图片
-
if (ANativeWindow_lock(theNativeWindow, &wbuffer, NULL) < 0) {
-
printf("---------Unable to lock window buffer");
-
//return -1;
-
}
-
/*
-
printf("linesize = %d linesize1 = %d linesize2 = %d linesize3 = %dn",picture.linesize[0],picture.linesize[1],picture.linesize[2],picture.linesize[3] );
-
memcpy(wbuffer.bits, picture.data[0], picture.linesize[0] * wbuffer.height);
-
memcpy(wbuffer.bits+picture.linesize[0] * wbuffer.height, picture.data[1], picture.linesize[1] * wbuffer.height/2);
-
memcpy(wbuffer.bits+picture.linesize[0] * wbuffer.height +picture.linesize[1] * wbuffer.height/2, picture.data[2], picture.linesize[2] * wbuffer.height/2);
-
*/
-
// printf("linesize = %d linesize1 = %d linesize2 = %d linesize3 = %d ph = %d pw = %dn",pFrame->linesize[0],pFrame->linesize[1],pFrame->linesize[2],pFrame->linesize[3],pFrame->height,pFrame->width);
-
-
// resample_yv12((guchar*)wbuffer.bits,wbuffer.width,wbuffer.height, (guchar*)pdata, pFrame->width, pFrame->height, SCALE_TYEP_NEAREST);
-
get_yuvdata(wbuffer.bits,pFrame);
-
ANativeWindow_unlockAndPost(theNativeWindow);
这种写缓冲方式编译简单,适用平台广。
用FFMPEG的方案适用数据流的格式多,surface刷写方式。但是直接使用CPU解码,软解的性能依赖于CPU的性能,且功耗较高。
为改善效率问题,试过好几种方式,如转换成YUV数据,不进行RGB转换,然后直接显示420SP(surfaceview.getholder.setformat).或者解码后通过BILINEAR算法缩放而不使用
sws_scale函数,当然效果均不太理想,遂有了第三种方案
方案三:管道+OpenAL
此方案是直接参考NDK实例代码native-media,就不贴代码了。
优点是使用Android底层解码库,基本使用gpu解码,速度快,功耗小。不足就是仅支持TS流,且显示无法全部控制,以及依赖底层实现,部分设备无法流畅播放,且无法DEBUG。
其实最好的方式是在SERVER建立RTSP服务,当然还需在接收端使用LIVE555转码,不过这种方式不符合后面特殊的加密需求。
阅读(7078) | 评论(0) | 转发(0) |