【Android音视频】Android TS流数据解析和回调

为了解析传输到手机的TS流有效视频数据,进行预览播放和录制等功能,在网上看到的部分关于android播放TS流,如UDP预览播放,采取的方式真是够标新立异,如通过保存到本地ts文件然后播放,会有闪屏现象等,当然也有很多的播放器支持播放,如ffmepg-android,vlc-android,vitamio等等。我感觉这些库要么太大,要么有很多都不满足我的需求,所以我自己尝试学习和通过jni调用c++去解析ts流

以下文章是我本文学习并参考的部分感觉非常不错的,希望大家学习时可以看看,方便全面学习:

一、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

  • 部分参数或者结构说明我在代码注释中给出

    图片来源:https://www.cnblogs.com/jiayayao/p/6832614.html

4. 解析流程:具体的对应结构在我上面列出的参考文章中都讲解的非常详细,本文主要写一个简单流程引导,做到一个快速集成到项目的目的
  1. 遍历TS流,通过同步字节查到ts header,sync byte: 1B,其值固定为0x47(需要考虑差错,buff拼接的情况)
  2. 获取PAT
  3. 根据PAT查询的PMT_PID查询对应的PMT的表
  4. 根据PMT查询对应的VEDIO_PID和AUDIO_PID
  5. 对应的PID解析视频和音频ParsePES
  6. 以下为解析流程结构图:
    2.png

二、TS流解析代码

本文给出的TS解析代码根据项目https://github.com/js2854/TSParser改动得来,该开源项目主要实现对TS文件的解析和各种信息的打印,我这边参考添加的改动:更改为TS流实现相应解析,增加的PES-音视频有效数据的解析,并通过jni输出到java层,添加android jni实现,数据缓存buff等,详细的方法都有部分注释,如有不明白,错误或侵权方面的问题请私信我,谢谢

  • Application.mk

      APP_PROJECT_PATH := $(call my-dir)
      APP_BUILD_SCRIPT := $(call my-dir)/Android.mk
      APP_ABI := armeabi armeabi-v7a
      APP_PLATFORM=android-23
    
  • Android.mk

      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, &param);
      	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
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值