海思3559万能平台搭建:RTSP实时播放的支持

简介: RTSP实时播放的支持

前言

 想搭建功能完备的万能平台,rtsp的支持自然必不可少。不论是编码的h264和我们用来实时传输的rtsp,都是可以单拎出来作为研究方向的,所幸在功能支持上而言,暂时全部当做黑盒子调用,还是比较容易实现的。看到过这样一句话,知识是学不完的,选取我们有需要的即可。

RTSP

 实时流传输协议(Real Time Streaming Protocol,RTSP),RFC2326(中文版),是TCP/IP协议体系中的一个应用层协议,由哥伦比亚大学、网景和RealNetworks公司提交的IETF RFC标准。该协议定义了一对多应用程序如何有效地通过IP网络传送多媒体数据。

 RTSP是 TCP/IP 协议体系中的一个应用层协议,该协议定义了一对多应用程序如何有效地通过 IP 网络传送多媒体数据。RTSP在体系结构上位于RTP和RTCP之上,它使用TCP或UDP完成数据传输。HTTP与RTSP相比,HTTP传送HTML,而RTSP传送的是多媒体数据。

f07070fe96804bf0bad6c19c7c53e98f.png

 RTSP是基于文本的协议,采用ISO10646字符集,使用UTF-8编码方案。行以CRLF中断,包括消息类型、消息头、消息体和消息长。但接收者本身可将CR和LF解释成行终止符。基于文本的协议使其以自描述方式增加可选参数更容易,接口中采用SDP作为描述语言。

 RTSP是应用级协议,控制实时数据的发送。RTSP提供了一个可扩展框架,使实时数据,如音频与视频的受控点播成为可能。数据源包括现场数据与存储在剪辑中数据。该协议目的在于控制多个数据发送连接,为选择发送通道,如UDP、组播UDP与TCP,提供途径,并为选择基于RTP上发送机制提供方法。

 RTSP建立并控制一个或几个时间同步的连续流媒体。尽管连续媒体流与控制流交换是可能的,通常它本身并不发送连续流。换言之,RTSP充当多媒体服务器的网络远程控制。RTSP连接没有绑定到传输层连接,如TCP。在RTSP连接期间,RTSP用户可打开或关闭多个对服务器的可传输连接以发出RTSP请求。此外,可使用无连接传输协议,如UDP。RTSP流控制的流可能用到RTP,但RTSP操作并不依赖用于携带连续媒体的传输机制。

 简单一扫百度百科,瞬间云里雾里,不考虑技术实现方式,大白话说,就是实现了将编码完成后的音视频文件进行了实时的播放而已,我们的sample已经自带了保存编码完成后的音视频文件功能,只需要调用rtsp库,完成功能的支持其实就可以了

移植过程

 之前venc 的sample分析里已经讲到SAMPLE_COMM_VENC_StartGetStream是用来保存文件的函数,不幸的是,这个函数是在common目录下的,Makefile在编译时会将这一目录下的所有文件加进来,为了不必要的麻烦,除了添加注释之外,一般不要擅自更改这些地方的函数

 另外,出于个人习惯,将原sample的中所有可能修改的都重新参考sample写一遍,一是可以加深印象,而来修改起来非常方便,不用估计会给其他的sample文件带来影响

编码文件保存

pthread_t gs_RtspVencPid;
 static SAMPLE_VENC_GETSTREAM_PARA_S gs_stPara;
/* 
 *描述  :调用RTSP静态库后,用于实时显示编码后图像
 *参数  :VeChn[] 编码通道号
          s32Cnt 通道数
 *返回值:创建线程PLATFORM_VENC_GetVencStreamRtsp,传递结构体gs_stPara
 *注意  :无
 */
HI_S32 PLATFORM_VENC_StartGetStreamRtsp(VENC_CHN VeChn[],HI_S32 s32Cnt)
{
    HI_U32 i;
    gs_stPara.bThreadStart = HI_TRUE;
    gs_stPara.s32Cnt = s32Cnt;
    for(i=0; i<s32Cnt; i++)
    {
        gs_stPara.VeChn[i] = VeChn[i];
    }
    return pthread_create(&gs_RtspVencPid, 0, PLATFORM_VENC_GetVencStreamRtsp, (HI_VOID*)&gs_stPara);
}
/******************************************************************************
*描述  :结束RTSP实时播放线程
*参数  :无
*返回值:成功返回0
*注意  :无
******************************************************************************/
HI_S32 PLATFORM_VENC_StopGetStreamRtsp(void)
{
    if (HI_TRUE == gs_stPara.bThreadStart)
    {
        gs_stPara.bThreadStart = HI_FALSE;
        pthread_join(gs_RtspVencPid, 0);
    }
    return HI_SUCCESS;
}
/******************************************************************************
 *描述  :保存文件
 *参数  :pFd 文件描述符
          pstStream 帧码流类型结构体。
 *返回值:成功返回0
 *注意  :无
******************************************************************************/
HI_S32 PLATFORM_VENC_SaveStream(FILE* pFd, VENC_STREAM_S* pstStream)
{
    HI_S32 i;
    for (i = 0; i < pstStream->u32PackCount; i++)
    {
        fwrite(pstStream->pstPack[i].pu8Addr + pstStream->pstPack[i].u32Offset,
               pstStream->pstPack[i].u32Len - pstStream->pstPack[i].u32Offset, 1, pFd);
        fflush(pFd);
    }
    return HI_SUCCESS;
}
/******************************************************************************
 *描述  :根据enPayload获取文件后缀
 *参数  :enPayload 音视频净荷类型枚举
          szFilePostfix 文件前缀
 *返回值:成功返回0,失败返回-1
******************************************************************************/
HI_S32 PLATFORM_VENC_GetFilePostfix(PAYLOAD_TYPE_E enPayload, char* szFilePostfix)
{
    if (PT_H264 == enPayload)
    {
        strcpy(szFilePostfix, ".h264");
    }
    else if (PT_H265 == enPayload)
    {
        strcpy(szFilePostfix, ".h265");
    }
    else if (PT_JPEG == enPayload)
    {
        strcpy(szFilePostfix, ".jpg");
    }
    else if (PT_MJPEG == enPayload)
    {
        strcpy(szFilePostfix, ".mjp");
    }
    else if (PT_PRORES == enPayload)
    {
        strcpy(szFilePostfix, ".prores");
    }
    else
    {
        SAMPLE_PRT("payload type err!\n");
        return HI_FAILURE;
    }
    return HI_SUCCESS;
}
/* 
 *描述  :调用RTSP静态库后,用于实时显示编码后图像
 *参数  :p 线程传递进来的结构体指针原型为 SAMPLE_VENC_GETSTREAM_PARA_S
 *返回值:NULL
 *注意  :无
 */
HI_VOID* PLATFORM_VENC_GetVencStreamRtsp(HI_VOID* p)
{
    HI_S32 i;
    HI_S32 s32ChnTotal;
    VENC_CHN_ATTR_S stVencChnAttr;
    SAMPLE_VENC_GETSTREAM_PARA_S* pstPara;
    HI_S32 maxfd = 0;
    struct timeval TimeoutVal;
    fd_set read_fds;
    HI_U32 u32PictureCnt[VENC_MAX_CHN_NUM]={0};
    HI_S32 VencFd[VENC_MAX_CHN_NUM];
    HI_CHAR aszFileName[VENC_MAX_CHN_NUM][64];
    FILE* pFile[VENC_MAX_CHN_NUM];
    char szFilePostfix[10];
    VENC_CHN_STATUS_S stStat;
    VENC_STREAM_S stStream;
    HI_S32 s32Ret;
    VENC_CHN VencChn;
    PAYLOAD_TYPE_E enPayLoadType[VENC_MAX_CHN_NUM];
    VENC_STREAM_BUF_INFO_S stStreamBufInfo[VENC_MAX_CHN_NUM];
    prctl(PR_SET_NAME, "GetVencStream", 0,0,0);
    pstPara = (SAMPLE_VENC_GETSTREAM_PARA_S*)p;
    s32ChnTotal = pstPara->s32Cnt;
    /******************************************
     step 1:  check & prepare save-file & venc-fd
     检查并准备保存文件和venc fd
    ******************************************/
    if (s32ChnTotal >= VENC_MAX_CHN_NUM)
    {
        SAMPLE_PRT("input count invaild\n");
        return NULL;
    }
    for (i = 0; i < s32ChnTotal; i++)
    {
        /* decide the stream file name, and open file to save stream 
        确定视频流文件名,并打开文件以保存视频流*/
        VencChn = pstPara->VeChn[i];
        s32Ret = HI_MPI_VENC_GetChnAttr(VencChn, &stVencChnAttr);
        if (s32Ret != HI_SUCCESS)
        {
            SAMPLE_PRT("HI_MPI_VENC_GetChnAttr chn[%d] failed with %#x!\n", \
                       VencChn, s32Ret);
            return NULL;
        }
        enPayLoadType[i] = stVencChnAttr.stVencAttr.enType;
        s32Ret = PLATFORM_VENC_GetFilePostfix(enPayLoadType[i], szFilePostfix);
        if (s32Ret != HI_SUCCESS)
        {
            SAMPLE_PRT("PLATFORM_VENC_GetFilePostfix [%d] failed with %#x!\n", \
                       stVencChnAttr.stVencAttr.enType, s32Ret);
            return NULL;
        }
        if(PT_JPEG != enPayLoadType[i])
        {
            snprintf(aszFileName[i],32, "./RTSP/RTSP_chn%d%s", i, szFilePostfix);
            pFile[i] = fopen(aszFileName[i], "wb");
            if (!pFile[i])
            {
                SAMPLE_PRT("open file[%s] failed!\n",
                           aszFileName[i]);
                return NULL;
            }
        }
        /* Set Venc Fd. */
        VencFd[i] = HI_MPI_VENC_GetFd(i);
        if (VencFd[i] < 0)
        {
            SAMPLE_PRT("HI_MPI_VENC_GetFd failed with %#x!\n",
                       VencFd[i]);
            return NULL;
        }
        if (maxfd <= VencFd[i])
        {
            maxfd = VencFd[i];
        }
        s32Ret = HI_MPI_VENC_GetStreamBufInfo (i, &stStreamBufInfo[i]);
        if (HI_SUCCESS != s32Ret)
        {
            SAMPLE_PRT("HI_MPI_VENC_GetStreamBufInfo failed with %#x!\n", s32Ret);
            return (void *)HI_FAILURE;
        }
    }
    /******************************************
     step 2:  Start to get streams of each channel.
     开始获取每个通道的视频流
    ******************************************/
    while (HI_TRUE == pstPara->bThreadStart)
    {
        FD_ZERO(&read_fds);
        for (i = 0; i < s32ChnTotal; i++)
        {
            FD_SET(VencFd[i], &read_fds);
        }
        TimeoutVal.tv_sec  = 2;
        TimeoutVal.tv_usec = 0;
        s32Ret = select(maxfd + 1, &read_fds, NULL, NULL, &TimeoutVal);
        if (s32Ret < 0)
        {
            SAMPLE_PRT("select failed!\n");
            break;
        }
        else if (s32Ret == 0)
        {
            SAMPLE_PRT("get venc stream time out, exit thread\n");
            continue;
        }
        else
        {
            for (i = 0; i < s32ChnTotal; i++)
            {
                if (FD_ISSET(VencFd[i], &read_fds))
                {
                    /*******************************************************
                     step 2.1 : query how many packs in one-frame stream.
                     查询每个帧流中有多少包。
                    *******************************************************/
                    memset(&stStream, 0, sizeof(stStream));
                    s32Ret = HI_MPI_VENC_QueryStatus(i, &stStat);
                    if (HI_SUCCESS != s32Ret)
                    {
                        SAMPLE_PRT("HI_MPI_VENC_QueryStatus chn[%d] failed with %#x!\n", i, s32Ret);
                        break;
                    }
                    /*******************************************************
                    step 2.2 :suggest to check both u32CurPacks and u32LeftStreamFrames at the same time,for example:
                    建议同时检查u32CurPacks和u32LeftStreamFrames
                     if(0 == stStat.u32CurPacks || 0 == stStat.u32LeftStreamFrames)
                     {
                        SAMPLE_PRT("NOTE: Current  frame is NULL!\n");
                        continue;
                     }
                    *******************************************************/
                    if(0 == stStat.u32CurPacks)
                    {
                          SAMPLE_PRT("NOTE: Current  frame is NULL!\n");
                          continue;
                    }
                    /*******************************************************
                     step 2.3 : malloc corresponding number of pack nodes.
                     malloc对应的包节点数。
                    *******************************************************/
                    stStream.pstPack = (VENC_PACK_S*)malloc(sizeof(VENC_PACK_S) * stStat.u32CurPacks);
                    if (NULL == stStream.pstPack)
                    {
                        SAMPLE_PRT("malloc stream pack failed!\n");
                        break;
                    }
                    /*******************************************************
                     step 2.4 : call mpi to get one-frame stream
                     调用mpi获取一个帧流
                    *******************************************************/
                    stStream.u32PackCount = stStat.u32CurPacks;
                    s32Ret = HI_MPI_VENC_GetStream(i, &stStream, HI_TRUE);
                    if (HI_SUCCESS != s32Ret)
                    {
                        free(stStream.pstPack);
                        stStream.pstPack = NULL;
                        SAMPLE_PRT("HI_MPI_VENC_GetStream failed with %#x!\n", \
                                   s32Ret);
                        break;
                    }
                    /*******************************************************
                     step 2.5 : save frame to file
                     将框架保存到文件
                    *******************************************************/
                    if(PT_JPEG == enPayLoadType[i])
                    {
                        snprintf(aszFileName[i],32, "stream_chn%d_%d%s", i, u32PictureCnt[i],szFilePostfix);
                        pFile[i] = fopen(aszFileName[i], "wb");
                        if (!pFile[i])
                        {
                            SAMPLE_PRT("open file err!\n");
                            return NULL;
                        }
                    }
#ifndef __HuaweiLite__
                    s32Ret = PLATFORM_VENC_SaveStream(pFile[i], &stStream);
#else
                    s32Ret = SAMPLE_COMM_VENC_SaveStream_PhyAddr(pFile[i], &stStreamBufInfo[i], &stStream);
#endif
                    if (HI_SUCCESS != s32Ret)
                    {
                        free(stStream.pstPack);
                        stStream.pstPack = NULL;
                        SAMPLE_PRT("save stream failed!\n");
                        break;
                    }
                    /*******************************************************
                     step 2.6 : release stream
                     释放视频流
                     *******************************************************/
                    s32Ret = HI_MPI_VENC_ReleaseStream(i, &stStream);
                    if (HI_SUCCESS != s32Ret)
                    {
                        SAMPLE_PRT("HI_MPI_VENC_ReleaseStream failed!\n");
                        free(stStream.pstPack);
                        stStream.pstPack = NULL;
                        break;
                    }
                    /*******************************************************
                     step 2.7 : free pack nodes
                     释放包节点
                    *******************************************************/
                    free(stStream.pstPack);
                    stStream.pstPack = NULL;
                    u32PictureCnt[i]++;
                    if(PT_JPEG == enPayLoadType[i])
                    {
                        fclose(pFile[i]);
                    }
                }
            }
        }
    }
    /*******************************************************
    * step 3 : close save-file
    *******************************************************/
    for (i = 0; i < s32ChnTotal; i++)
    {
        if(PT_JPEG != enPayLoadType[i])
        {
            fclose(pFile[i]);
        }
    }
    return NULL;
}

MakeFile修改

 rtsp库我们更改交叉编译环境(修改rtsp库的Makefile),重新编译生成静态库后,修改海思的Makefile,添加:

RTSP_DIR ?= $(PWD)/../../rtsp_lib
INC_FLAGS += -I$(RTSP_DIR)
SENSOR_LIBS += $(REL_LIB)/librtsp.a

 路径完全看自己放的位置所决定,静态库暂时放在了默认位置,后续可以改动

RTSP移植

 参考demo定义加载文件结构体,后续会升级!

#define MAX_SESSION_NUM 8 //rtsp最大接口数
#define DEMO_CFG_FILE "platform.ini"
/* 配置文件结构体 */
typedef struct demo_cfg_para
{
    int session_count;
    struct {
        char path[64];
        char video_file[64];
        char audio_file[64];
    } session_cfg[MAX_SESSION_NUM];
}demo_cfg;
1
2
3
4
5
6
7
8
9
10
11
12
13
static int flag_run = 1;
static void sig_proc(int signo)
{
    flag_run = 0;
}
static int get_next_video_frame (FILE *fp, uint8_t **buff, int *size)
{
    uint8_t szbuf[1024];
    int szlen = 0;
    int ret;
    if (!(*buff)) {
        *buff = (uint8_t*)malloc(2*1024*1024);
        if (!(*buff))
            return -1;
    }
    *size = 0;
    while ((ret = fread(szbuf + szlen, 1, sizeof(szbuf) - szlen, fp)) > 0) {
        int i = 3;
        szlen += ret;
        while (i < szlen - 3 && !(szbuf[i] == 0 &&  szbuf[i+1] == 0 && (szbuf[i+2] == 1 || (szbuf[i+2] == 0 && szbuf[i+3] == 1)))) i++;
        memcpy(*buff + *size, szbuf, i);
        *size += i;
        memmove(szbuf, szbuf + i, szlen - i);
        szlen -= i;
        if (szlen > 3) {
            //printf("szlen %d\n", szlen);
            fseek(fp, -szlen, SEEK_CUR);
            break;
        }
    }
    if (ret > 0)
        return *size;
    return 0;
}
static int get_next_audio_frame (FILE *fp, uint8_t **buff, int *size)
{
    int ret;
#define AUDIO_FRAME_SIZE 320
    if (!(*buff)) {
        *buff = (uint8_t*)malloc(AUDIO_FRAME_SIZE);
        if (!(*buff))
            return -1;
    }
    ret = fread(*buff, 1, AUDIO_FRAME_SIZE, fp);
    if (ret > 0) {
        *size = ret;
        return ret;
    }
    return 0;
}
int load_cfg(demo_cfg *cfg, const char *cfg_file)
{
//cfgline: path=%s video=%s audio=%s
    FILE *fp = fopen(cfg_file, "r");
    char line[256];
    int count = 0;
    if (!fp) {
        fprintf(stderr, "open %s failed\n", cfg_file);
        return -1;
    }
    memset(cfg, 0, sizeof(*cfg));
    while (fgets(line, sizeof(line) - 1, fp)) {
        const char *p;
        memset(&cfg->session_cfg[count], 0, sizeof(cfg->session_cfg[count]));
        if (line[0] == '#')
            continue;
        p = strstr(line, "path=");
        if (!p)
            continue;
        if (sscanf(p, "path=%s", cfg->session_cfg[count].path) != 1)
            continue;
        if ((p = strstr(line, "video="))) {
            if (sscanf(p, "video=%s", cfg->session_cfg[count].video_file) != 1) {
                fprintf(stderr, "parse video file failed %s\n", p);
            }
        }
        if ((p = strstr(line, "audio="))) {
            if (sscanf(p, "audio=%s", cfg->session_cfg[count].audio_file) != 1) {
                fprintf(stderr, "parse audio file failed %s\n", p);
            }
        }
        if (strlen(cfg->session_cfg[count].video_file) || strlen(cfg->session_cfg[count].audio_file)) {
            count ++;
        } else {
            fprintf(stderr, "parse line %s failed\n", line);
        }
    }
    cfg->session_count = count;
/*
path=/live/chn0 video=BarbieGirl.h264 audio=BarbieGirl.alaw
path=/live/chn1 video=BarbieGirl.h264
path=/live/chn2 audio=BarbieGirl.alaw
*/
    printf("cfg->session_count:%d\n",cfg->session_count);//3
    fclose(fp);
    return count;
}
/* 
 *描述  :用于rtsp实时播放的线程
 *参数  :NULL
 *返回值:无
 *注意  :加载文件demo.cfg  path=/mnt/sample/venc/RTSP video=RTSP_chn1.h264
 */
void *video_play_rtsp_task(void*arg)
{
    const char *cfg_file = DEMO_CFG_FILE;
  demo_cfg cfg;
  FILE *fp[MAX_SESSION_NUM][2] = {{NULL}};
  rtsp_demo_handle demo;
  rtsp_session_handle session[MAX_SESSION_NUM] = {NULL};
  int session_count = 0;
  uint8_t *vbuf = NULL;
  uint8_t *abuf = NULL;
  uint64_t ts = 0;
  int vsize = 0, asize = 0;
  int ret, ch;
    ret = load_cfg(&cfg, cfg_file);
  demo = rtsp_new_demo(8554);//rtsp sever socket
  if (NULL == demo) {
  SAMPLE_PRT("rtsp new demo failed!\n");
  return 0;
  }
  session_count = 1;
    for (ch = 0; ch < session_count; ch++) 
  {
  if (strlen(cfg.session_cfg[ch].video_file)) {
    fp[ch][0] = fopen(cfg.session_cfg[ch].video_file, "rb");//打开视频文件
    if (!fp[ch][0]) {
    fprintf(stderr, "open %s failed\n", cfg.session_cfg[ch].video_file);
    }
  }
         //fp[ch][1] :音频文件的句柄
  // if (strlen(cfg.session_cfg[ch].audio_file)) {
  //  fp[ch][1] = fopen(cfg.session_cfg[ch].audio_file, "rb");
  //  if (!fp[ch][1]) {
  //    fprintf(stderr, "open %s failed\n", cfg.session_cfg[ch].audio_file);
  //  }
  // }
  if (fp[ch][0] == NULL && fp[ch][1] == NULL)
    continue;
  session[ch] = rtsp_new_session(demo, cfg.session_cfg[ch].path);//对应rtsp session 
  if (NULL == session[ch]) {
    printf("rtsp_new_session failed\n");
    continue;
  }
  if (fp[ch][0]) {//当前请求路径存视频数据源
    rtsp_set_video(session[ch], RTSP_CODEC_ID_VIDEO_H264, NULL, 0);
    rtsp_sync_video_ts(session[ch], rtsp_get_reltime(), rtsp_get_ntptime());
  }
  printf("==========> rtsp://192.168.119.200:8554%s for %s  <===========\n", cfg.session_cfg[ch].path, 
    fp[ch][0] ? cfg.session_cfg[ch].video_file : "");
  }
  ts = rtsp_get_reltime();
  signal(SIGINT, sig_proc);
  while (flag_run) {
  uint8_t type = 0;
  for (ch = 0; ch < session_count; ch++) {//3个源
    if (fp[ch][0]) {
    read_video_again:
    ret = get_next_video_frame(fp[ch][0], &vbuf, &vsize);
    if (ret < 0) {
      fprintf(stderr, "get_next_video_frame failed\n");
      flag_run = 0;
      break;
    }
    if (ret == 0) {
      fseek(fp[ch][0], 0, SEEK_SET);
      if (fp[ch][1])
      fseek(fp[ch][1], 0, SEEK_SET);
      goto read_video_again;
    }
    if (session[ch])//1源session 存存
      rtsp_tx_video(session[ch], vbuf, vsize, ts);//2rtsp_client_connect存在
    type = 0;
    if (vbuf[0] == 0 && vbuf[1] == 0 && vbuf[2] == 1) {
      type = vbuf[3] & 0x1f;
    }
    if (vbuf[0] == 0 && vbuf[1] == 0 && vbuf[2] == 0 && vbuf[3] == 1) {
      type = vbuf[4] & 0x1f;
    }
    if (type != 5 && type != 1)
      goto read_video_again;
    }
    if (fp[ch][1]) {
    ret = get_next_audio_frame(fp[ch][1], &abuf, &asize);
    if (ret < 0) {
      fprintf(stderr, "get_next_audio_frame failed\n");
      break;
    }
    if (ret == 0) {
      fseek(fp[ch][1], 0, SEEK_SET);
      if (fp[ch][0])
      fseek(fp[ch][0], 0, SEEK_SET);
      continue;
    }
    if (session[ch])
      rtsp_tx_audio(session[ch], abuf, asize, ts);
    }
  }
        do {
            ret = rtsp_do_event(demo);//
            if (ret > 0)
                continue;
            if (ret < 0)
                break;
            usleep(20000);
        } while (rtsp_get_reltime() - ts < 1000000 / 25);
  if (ret < 0)
    break;
  ts += 1000000 / 25;
  printf(".");fflush(stdout);//立即将printf的数据输出显示
  }
  free(vbuf);
  free(abuf);
  for (ch = 0; ch < session_count; ch++) {
  if (fp[ch][0])
    fclose(fp[ch][0]);
  if (fp[ch][1])
    fclose(fp[ch][1]);
  if (session[ch])
    rtsp_del_session(session[ch]);
  }
  rtsp_del_demo(demo);
  printf("Exit.\n");
  getchar();
    return 0;
}

改进

 这样的应用方式会依赖码流的本地保存,编码的速率影响保存速率,从保存文件的读取在实时播放,这样的流程还是很繁琐的,所以造成了大量的延时,直接从根源处,保存之前获取码流才是更为正确的选择,具体做法参考RTSP优化篇


相关文章
|
Web App开发 数据采集 物联网
Android平台基于RTMP或RTSP的一对一音视频互动技术方案探讨
随着智能门禁等物联网产品的普及,越来越多的开发者对音视频互动体验提出了更高的要求。目前市面上大多一对一互动都是基于WebRTC,优点不再赘述,我们这里先说说可能需要面临的问题:WebRTC的服务器部署非常复杂,可以私有部署,但是非常复杂。传输基于UDP,很难保证传输质量,由于UDP是不可靠的传输协议,在复杂的公网网络环境下,各种突发流量、偶尔的传输错误、网络抖动、超时等等都会引起丢包异常,都会在一定程度上影响音视频通信的质量,难以应对复杂的互联网环境,如跨区跨运营商、低带宽、高丢包等场景,行话说的好:从demo到实用,中间还差1万个WebRTC。
150 0
|
8天前
|
编解码 vr&ar 图形学
|
Web App开发 编解码 安全
在高版本谷歌Chrome浏览器中用VLC播放海康、大华RTSP实时视频完全方案
随着互联网基础设施的完善以及4G、5G等技术的大规模商用,在Chrome、Firefox、Edge等浏览器播放RTSP视频流也慢慢成为了信息化系统的行业标准。早些年还可用VLC播放器在网页中播放RTSP视频流,好景不长,2015年Chrome、Firefox等浏览器取消了对 NPAPI插件的支持,导致在高版本的Chrome等网页中播放海康威视、大华等摄像头RTSP视频流也成了奢望。
2351 0
|
Web App开发 编解码 算法
发现一个非常好用的RTC(实时音视频通信)方案,做直播和视频通话都很牛
HaaS RTC是阿里云IoT联合视频云开发的IoT设备端上的实时通讯服务,主要面向直播,音视频通话等各种场景。
2193 0
发现一个非常好用的RTC(实时音视频通信)方案,做直播和视频通话都很牛
|
2月前
|
Web App开发 网络协议 Android开发
Android平台一对一音视频通话方案大比拼:WebRTC VS RTMP VS RTSP,谁才是王者?
【9月更文挑战第4天】本文详细对比了在Android平台上实现一对一音视频通话时常用的WebRTC、RTMP及RTSP三种技术方案。从技术原理、性能表现与开发难度等方面进行了深入分析,并提供了示例代码。WebRTC适合追求低延迟和高质量的场景,但开发成本较高;RTMP和RTSP则在简化开发流程的同时仍能保持较好的传输效果,适用于不同需求的应用场景。
128 1
|
2月前
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
|
3月前
|
编解码 vr&ar 图形学
惊世骇俗!Unity下如何实现低至毫秒级的全景RTMP|RTSP流渲染,颠覆你的视觉体验!
【8月更文挑战第14天】随着虚拟现实技术的进步,全景视频作为一种新兴媒体形式,在Unity中实现低延迟的RTMP/RTSP流渲染变得至关重要。这不仅能够改善用户体验,还能广泛应用于远程教育、虚拟旅游等实时交互场景。本文介绍如何在Unity中实现全景视频流的低延迟渲染,并提供代码示例。首先确保Unity开发环境及所需插件已就绪,然后利用`unity-rtsp-rtmp-client`插件初始化客户端并设置回调。通过FFmpeg等工具解码视频数据并更新至全景纹理,同时采用硬件加速、调整缓冲区大小等策略进一步降低延迟。此方案需考虑网络状况与异常处理,确保应用程序的稳定性和可靠性。
61 1
|
3月前
|
数据采集 编解码 开发工具
Android平台实现无纸化同屏并推送RTMP或轻量级RTSP服务(毫秒级延迟)
一个好的无纸化同屏系统,需要考虑的有整体组网、分辨率、码率、实时延迟、音视频同步和连续性等各个指标,做容易,做好难
|
3月前
|
Web App开发 网络协议 Android开发
### 惊天对决!Android平台一对一音视频通话方案大比拼:WebRTC VS RTMP VS RTSP,谁才是王者?
【8月更文挑战第14天】随着移动互联网的发展,实时音视频通信已成为移动应用的关键部分。本文对比分析了Android平台上WebRTC、RTMP与RTSP三种主流技术方案。WebRTC提供端到端加密与直接数据传输,适于高质量低延迟通信;RTMP适用于直播场景,但需服务器中转;RTSP支持实时流播放,但在复杂网络下稳定性不及WebRTC。三种方案各有优劣,WebRTC功能强大但集成复杂,RTMP和RTSP实现较简单但需额外编码支持。本文还提供了示例代码以帮助开发者更好地理解和应用这些技术。
142 0
|
6月前
|
Web App开发 移动开发 前端开发
web端实现rtsp实时推流视频播放可行性方案
总之,要在Web端实现RTSP实时推流视频播放,需要使用适当的前端技术(如HTML5 Video或WebRTC),以及媒体服务器或流转换器来处理RTSP流。这需要一些开发和配置工作,但是可以实现实时视频流的播放。具体的实现方案可能会根据您的需求和技术栈而有所不同,所以需要仔细评估和选择适合您的解决方案。
806 0