文章目录
为了解析传输到手机的TS流有效视频数据,进行预览播放和录制等功能,在网上看到的部分关于android播放TS流,如UDP预览播放,采取的方式真是够标新立异,如通过保存到本地ts文件然后播放,会有闪屏现象等,当然也有很多的播放器支持播放,如ffmepg-android,vlc-android,vitamio等等。我感觉这些库要么太大,要么有很多都不满足我的需求,所以我自己尝试学习和通过jni调用c++去解析ts流
以下文章是我本文学习并参考的部分感觉非常不错的,希望大家学习时可以看看,方便全面学习:
- MediaRecorder系列之StagefrightRecorder录制TS流flow(一)
- TS流讲解–什么是ts流
- TS流基本概念(以下ts结构图来源于此文章)
- 流媒体基础知识TS流 PS流 ES流区别
- TS码流格式分析
- 以下解析代码由此项目改动优化部分得到:https://github.com/js2854/TSParser
一、TS流简介
1. 什么是TS流 :
TS(Transport Stream,传输流),全称则是MPEG2-TS,主要应用于实时传送的节目,如实时广播的电视节目,机顶盒等。它的主要格式分别为h264/mpeg4,acc/MP3等,MPEG2-TS格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。
2. 在学习TS流是需要了解的部分定义:
-
ES流:基本码流,不分段的音频、视频或其他信息的连续码流。
-
PES流:分包的ES流,通过添加PES头进行标记,PES包的长度是可变的
-
TS流:传输流,固定长度的封包(188B),便于解析和恢复错包,它包含三个部分:ts header、adaptation field、payload,如下图结构,ts header通过PID去识别数据包的内容,adaptation field为补充内容,payload即我们需要解析的pes数据。
- 需要注意的是,一端TS流里面可能包含多个节目,这些在解析PAT和PMT时可以通过打印信息得到,在我代码里有注释,我的项目里固定只包含了一个,所以适配代码需要自己改动
3. 解析TS流的重点在于理解他的表结构:解析TS流的流程主要是通过对应的PID去分布解析我们需要的信息,从而截取出对应的有效数据
-
节目关联表Program Association Table (PAT) 0x0000,通过PAT我们可以解析对应的PMT表的PID
-
节目映射表Program Map Tables (PMT) 在PMT中解析出对应的视频和音频的PID值
-
条件接收表Conditional Access Table (CAT) 0x0001
-
网络信息表Network Information Table(NIT) 0x0010
-
部分参数或者结构说明我在代码注释中给出
4. 解析流程:具体的对应结构在我上面列出的参考文章中都讲解的非常详细,本文主要写一个简单流程引导,做到一个快速集成到项目的目的
- 遍历TS流,通过同步字节查到ts header,sync byte: 1B,其值固定为0x47(需要考虑差错,buff拼接的情况)
- 获取PAT
- 根据PAT查询的PMT_PID查询对应的PMT的表
- 根据PMT查询对应的VEDIO_PID和AUDIO_PID
- 对应的PID解析视频和音频ParsePES
- 以下为解析流程结构图:
二、TS流解析代码
本文给出的TS解析代码根据项目https://github.com/js2854/TSParser改动得来,该开源项目主要实现对TS文件的解析和各种信息的打印,我这边参考添加的改动:更改为TS流实现相应解析,增加的PES-音视频有效数据的解析,并通过jni输出到java层,添加android jni实现,数据缓存buff等,详细的方法都有部分注释,如有不明白,错误或侵权方面的问题请私信我,谢谢
-
APP_PROJECT_PATH := $(call my-dir) APP_BUILD_SCRIPT := $(call my-dir)/Android.mk APP_ABI := armeabi armeabi-v7a APP_PLATFORM=android-23
-
LOCAL_PATH := $(call my-dir) # Program include $(CLEAR_VARS) LOCAL_MODULE := tsparse LOCAL_SRC_FILES := jni_lib.cpp AACDecoder.cpp MFifo.cpp TSParser.cpp #LOCAL_C_INCLUDES := \ #$(MY_LOCAL_ANDSRC)/system/core/include \ #$(MY_LOCAL_ANDSRC)/frameworks/native/include \ #$(MY_LOCAL_ANDSRC)/hardware/libhardware/include #LOCAL_CFLAGS := -DHAVE_PTHREADS LOCAL_C_INCLUDES += $(LOCAL_PATH)/prebuilt/include LOCAL_LDLIBS := -llog -lz -lGLESv2 -landroid -lOpenSLES include $(BUILD_SHARED_LIBRARY)
-
jni_lib.cpp
#ifndef UINT64_C #define UINT64_C(c) (c ## ULL) #endif #include "mdebug.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <jni.h> #include <pthread.h> #include <unistd.h> #include <fcntl.h> #include "TSParser.h" static JavaVM *g_jvm = NULL; static TSParser * mpTSParser=NULL; pthread_mutex_t playMutex = PTHREAD_MUTEX_INITIALIZER; extern "C" { JNIEXPORT jint JNI_OnLoad(JavaVM * vm, void *reserved) { JNIEnv *env = NULL; jint result = -1; mInfo("JNI_OnLoad"); if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) return -1; g_jvm = vm; mpCamera = new CUCamera(); return JNI_VERSION_1_4; } } /// int OnDestroy() { if(mpTSParser){ mpTSParser->__stopThread(); if (mpTSParser->TsDoloopThreadHandle) pthread_join(mpTSParser->TsDoloopThreadHandle, NULL); mpTSParser->TsDoloopThreadHandle=NULL; delete mpTSParser; mpTSParser = NULL; } return 0; } static void *_tmain(void * cc) { if(mpTSParser!=NULL) mpTSParser->Parse(); } extern "C" { JNIEXPORT jint JNICALL Java_包名0_包名1_包名2_init(JNIEnv *env, jobject obj); JNIEXPORT jint JNICALL Java_包名0_包名1_包名2_JniLib_PushTsData(JNIEnv * env, jobject obj, jbyteArray jbArr,jint DataLen); JNIEXPORT void JNICALL Java_包名0_包名1_包名2_JniLib_initTS(JNIEnv *env,jobject obj); JNIEXPORT void JNICALL Java_包名0_包名1_包名2_JniLib_stopTsParse(JNIEnv *env,jobject obj); JNIEXPORT void JNICALL Java_包名0_包名1_包名2_JniLib_startTsParse(JNIEnv *env,jobject obj); } ; JNIEXPORT jint JNICALL Java_包名0_包名1_包名2_PushTsData(JNIEnv * env, jobject obj, jbyteArray jbArr,jint DataLen) { if(!mpTSParser) return -1; int ret = 0; jsize jlen = env->GetArrayLength(jbArr); jbyte* jbuf = env->GetByteArrayElements(jbArr, JNI_FALSE); char* buf = (char*)jbuf; mfxBitstreamTS *pBufTs = NULL; while(true){ pBufTs = mpTSParser->GetEmptyTsBuf(); if(pBufTs==NULL||pBufTs->Data==NULL) { usleep(1); continue; } break; } if (pBufTs == NULL ||pBufTs->Data == NULL) { return -2; } // mInfo("-----------------------PushTsFrame %d",DataLen); TS_TIMES memcpy(pBufTs->Data,(unsigned char *) jbuf, DataLen); pBufTs->DataLength = DataLen; mpTSParser->PushTsBuf(pBufTs); env->ReleaseByteArrayElements(jbArr, jbuf, 0); return ret; } JNIEXPORT void JNICALL Java_包名0_包名1_包名2_initTS(JNIEnv * env, jobject thiz) { if(mpTSParser==NULL) mpTSParser = new TSParser(); mpTSParser->initMemory(); mpTSParser->JavaMethodInit(g_jvm, thiz); return; } JNIEXPORT void JNICALL Java_包名0_包名1_包名2_stopTsParse(JNIEnv * env, jobject obj) { if (!mpTSParser) return ; mpTSParser->__stopThread(); if (mpTSParser->TsDoloopThreadHandle) pthread_join(mpTSParser->TsDoloopThreadHandle, NULL); mpTSParser->TsDoloopThreadHandle=NULL; delete mpTSParser; mpTSParser = NULL; } JNIEXPORT void JNICALL Java_包名0_包名1_包名2_startTsParse(JNIEnv * env,jobject obj ) { if (!mpTSParser) return ; int ret_t; struct sched_param param; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setschedpolicy(&attr, SCHED_RR); param.sched_priority = 90; pthread_attr_setschedparam(&attr, ¶m); ret_t = pthread_create(&mpTSParser->TsDoloopThreadHandle, &attr, _tmain,NULL); if (ret_t) { mLogW("pthread_create TsDoloopThreadHandle failed [%d] \n", ret_t); } } JNIEXPORT jint JNICALL Java_包名0_包名1_包名2_CameraLib_init(JNIEnv *env, jobject obj) { mpCamera->JavaMethodInit(g_jvm, obj); return 0; }
-
types.h 建议到参考开源项目中拷贝
-
TSParse.h
#ifndef __TS_PARSER_H__ #define __TS_PARSER_H__ struct _HANDLE_ { unsigned int code; void *pContext; }; #include <assert.h> #include <errno.h> #include <stdio.h> #include "mdebug.h" #include <string.h> #include <fcntl.h> #include "types.h" #include <string.h> #include <stdlib.h> #include "MFifo.h" using namespace std; typedef enum TS_ERR { TS_OK = 0, TS_IN_PARAM_ERR, TS_SYNC_BYTE_ERR, TS_FILE_OPEN_FAIL, TS_FILE_SEEK_FAIL, }TS_ERR; // PID种类 typedef enum E_PKT_TYPE { E_PAT = 0, E_PMT = 1, E_PCR = 2, E_AUDIO = 3, E_VIDEO = 4, E_NIT =5, E_SI =6, E_MAX = 7 }E_PKT_TYPE; class TSPacket { public: // uint8 * bufH264pkt;//=new uint8[1024*2000]; uint32 pktH264Len;//=0; uint32 pktAccLen;//=0; uint32 pktindex;//=0; bool get_PAT_Head=false; bool get_PMT_Head=false; mfxBitstreamTS *h264Buf; mfxBitstreamTS *__accBuf; TSPacket() : m_pBuf(NULL) , m_pHdr(NULL) , m_u16PID(PID_UNSPEC) , m_u8CC(0) , m_u16PMTPID(PID_UNSPEC) , m_u8StreamId(0) , m_s64PCR(INVALID_VAL) , m_s64PTS(INVALID_VAL) , m_s64DTS(INVALID_VAL) { // bufH264pkt=new uint8[1024*2000]; pktH264Len=0; pktindex=0; get_PAT_Head=false; get_PMT_Head=fals