RV1126 ISP 摄像头 AI 识别并直播到 B 站(bilibili)案例的实现

本帖最后由 799959745 于 2021-9-3 15:15 编辑

直播案例源码
直播案例源码可以看下我写的另一个帖子

https://dev.t-firefly.com/thread-104655-1-1.html

代码路径:sdk/app/firefly_rockx_demo/
源码名字:rockx_face_attribute_aenc_venc_rtsp_service.cpprockx_face_attribute_client.cpp
具体编译和使用参考上面的帖子链接。

直播步骤
1、开通 B 站直播间。
2、点击右上角的图像标志进入个人主页。
直播步骤1.png

3、鼠标移动到头像标志,在弹出的页面点击进入直播中心。

4、选择我的直播间。

5、在开播设置中,选择直播分类,填写房间标题。确认完毕之后点击开始直播。然后出现你的 rtmp 地址和你的直播码。这两个码是你推流到 B 站最重要的东西。
注意:只有点击开始直播才会出现这两个地址。

6、板子执行 alsamixer 打开声卡,设置 Capture MIC pathMain Mic。板子右下角的 MIC 接口需要自行接入麦克风

7、接下来就是重点了。执行程序然后推流到 B 站进行直播。

  • 手动执行:
rockx_face_attribute_aenc_venc_rtsp_service -c /usr/share/firefly_rockx_demo/rockx_app.cfg &
rockx_face_attribute_client -c /usr/share/firefly_rockx_demo/rockx_app.cfg &

ffmpeg -f rtsp -rtsp_transport tcp -i "rtsp://127.0.0.1:8554/H264_stream_0" -i "rtsp://127.0.0.1:8555/audio_stream_0" -c  copy -f flv "你的 rtmp 地址+你的直播码" &

8、直播效果。可看到摄像头识别画面和听到 MIC 音频播放的声音。

案例分析:
程序(rockx_face_attribute_aenc_venc_rtsp_service )实现摄像头视频的 rtsp 推流和 MIC 音频的 rtsp 推流。然后使用 ffmpeg 命令将视频流和音频流合并并且转换成 rtmp 流。该 rtmp 流是最终的推流地址。可将该地址更换到其他直播网站提供的 rtmp 地址。这样就可以实现其他平台的直播

点赞!!

本帖最后由 ubuntu 于 2021-9-13 17:25 编辑

亲自出镜,赞.

点赞

执行
ffmpeg -f rtsp -rtsp_transport tcp -i “rtsp://127.0.0.1:8554/H264_stream_0” -i “rtsp://127.0.0.1:8555/audio_stream_0” -c copy -f flv “rtmp://192.168.1.101/live/mainstream”
出现这个错误
Input #0, rtsp, from ‘rtsp://127.0.0.1:8554/H264_stream_0’:
Metadata:
title : rtsp_demo
Duration: N/A, start: 0.941667, bitrate: N/A
Stream #0:0: Video: h264 (Main), yuv420p(yuv420p) (progressive), 800x1280, 30 fps, 26.67 tbr, 90k tbn, 60 tbc
Input #1, rtsp, from ‘rtsp://127.0.0.1:8555/audio_stream_0’:
Metadata:
title : rtsp_demo
Duration: N/A, start: 0.000000, bitrate: N/A
Stream #1:0: Audio: aac (LC), 16000 Hz, stereo, fltp
[tcp @ 0x765b0] Connection to tcp://192.168.1.101:1935 failed: Connection refused
[rtmp @ 0x5bb30] Cannot open connection tcp://192.168.1.101:1935
rtmp://192.168.1.101/live/mainstream: Connection refused

请问这是什么原因

xiongyingsun 发表于 2022-1-20 10:00
执行
ffmpeg -f rtsp -rtsp_transport tcp -i “rtsp://127.0.0.1:8554/H264_stream_0” -i "rtsp://127.0. …

这个示例只有在 rv1126_rv1109_linux_20210904.xml 版本确认能用。
确认一下你的 sdk 版本:

cd sdk/
realpath .repo/manifest.xml

如果不是 rv1126_rv1109_linux_20210904.xml 。那就需要回退 sdk 代码再按照教程的操作来。

799959745 发表于 2022-1-20 11:45
这个示例只有在 rv1126_rv1109_linux_20210904.xml 版本确认能用。
确认一下你的 sdk 版本:
如果不是 …

我的版本是这样的
/home/tyzc/work/rv1126_rv1109_linux_release_20211022/.repo/manifests/rv1126_rv1109_linux/rv1126_rv1109_linux_20211225.xml

但是我是从
rockx_face_attribute_aenc_venc_rtsp_service.cpp代码里,把rtsp 音频视频的推流进程搞出来了,然后再vlc中视频事可以看的,音频有读取,但是没听到声音,rtsp 推流地址已经生成了,用ffmpeg 合成推出来,就出现错误了~

xiongyingsun 发表于 2022-1-20 14:11
我的版本是这样的
/home/tyzc/work/rv1126_rv1109_linux_release_20211022/.repo/manifests/rv1126_rv11 …

音频流 跟视频流都可以正常用vlc听到,但是音频只能听到声卡的声音,拾音器获取的外部声音又听不到, 很奇怪~

xiongyingsun 发表于 2022-1-20 14:11
我的版本是这样的
/home/tyzc/work/rv1126_rv1109_linux_release_20211022/.repo/manifests/rv1126_rv11 …

rv1126_rv1109_linux_20211225.xml 的 rkmedia 库更新了。而firefly_rockx_demo的仓库没有跟上更新。现在在维护这个仓库。这边也遇到了推流的问题。如果着急的话建议回退版本。

799959745 发表于 2022-1-20 14:33
rv1126_rv1109_linux_20211225.xml 的 rkmedia 库更新了。而firefly_rockx_demo的仓库没有跟上更新。现在 …

嗯哈,好的,目前我更改了,rk初始化的通道号,从2改成1,外部声音可以推了~不过想要rtsp 音频视频流同步推,用ffmpeg的方式好像不行,一直遇到问题,不知道大神有木有试过,推流已经创建号了,vlc都可以正常接,流应该是没问题了~

xiongyingsun 发表于 2022-1-20 15:07
嗯哈,好的,目前我更改了,rk初始化的通道号,从2改成1,外部声音可以推了~不过想要rtsp 音频视频流同步 …

你是根据 rockx_face_attribute_aenc_venc_rtsp_service 这个示例改的吗?方便发一份你修改的示例吗?这样这边也能快点修复这个错误。

799959745 发表于 2022-1-20 15:28
你是根据 rockx_face_attribute_aenc_venc_rtsp_service 这个示例改的吗?方便发一份你修改的示例吗?这 …

static void *AudioRtspStream(void *data) {
printf(“--------------------------------Get packet-AudioRtspStream-------into---------------------------\n”);
AudioParams *pstAudioParams = (AudioParams *)data;
RK_U8 aac_header[7];
int ret = 0;
RK_U32 u32SampleRate = 16000;
RK_U32 u32BitRate = 64000; // 64kbps
RK_U32 u32ChnCnt = 1;
RK_U32 u32FrameCnt = 1024; // always 1024 for aac
SAMPLE_FORMAT_E enSampleFmt = RK_SAMPLE_FMT_FLTP;
// default:CARD=rockchiprk809co
RK_CHAR *pDeviceName = “default”;
RK_CHAR *pOutPath = “/tmp/aenc.adts”;

AudioParams stAudioParams;
stAudioParams.u32SampleRate = u32SampleRate;
stAudioParams.u32ChnCnt = u32ChnCnt;
stAudioParams.enSampleFmt = enSampleFmt;
stAudioParams.pOutPath = pOutPath;

MPP_CHN_S mpp_chn_ai, mpp_chn_aenc;
mpp_chn_ai.enModId = RK_ID_AI;
mpp_chn_ai.s32ChnId = 0;
mpp_chn_aenc.enModId = RK_ID_AENC;
mpp_chn_aenc.s32ChnId = 0;

// 1. create AI
AI_CHN_ATTR_S ai_attr;
ai_attr.pcAudioNode = pDeviceName;
ai_attr.enSampleFormat = enSampleFmt;
ai_attr.u32NbSamples = u32FrameCnt;
ai_attr.u32SampleRate = u32SampleRate;
ai_attr.u32Channels = u32ChnCnt;
ai_attr.enAiLayout = AI_LAYOUT_NORMAL;
ret = RK_MPI_AI_SetChnAttr(mpp_chn_ai.s32ChnId, &ai_attr);
ret |= RK_MPI_AI_EnableChn(mpp_chn_ai.s32ChnId);
if (ret) {
    printf("Create AI[0] failed! ret=%d\n", ret);
    return NULL;
}

// 2. create AENC
AENC_CHN_ATTR_S aenc_attr;
aenc_attr.enCodecType = RK_CODEC_TYPE_AAC;
aenc_attr.u32Bitrate = u32BitRate;
aenc_attr.u32Quality = 1;
aenc_attr.stAencAAC.u32Channels = u32ChnCnt;
aenc_attr.stAencAAC.u32SampleRate = u32SampleRate;
ret = RK_MPI_AENC_CreateChn(mpp_chn_aenc.s32ChnId, &aenc_attr);
if (ret) {
    printf("Create AENC[0] failed! ret=%d\n", ret);
    return NULL;
}

// 3. bind AI-AENC
ret = RK_MPI_SYS_Bind(&mpp_chn_ai, &mpp_chn_aenc);
if (ret) {
    printf("Bind AI[0] to AENC[0] failed! ret=%d\n", ret);
    return NULL;
}

MEDIA_BUFFER buffer;
while(1) {
    buffer = RK_MPI_SYS_GetMediaBuffer(
            RK_ID_AENC, 0, -1);
    if (!buffer)
        continue;
    printf("#Get packet-AudioRtspStream, size %zu\n", RK_MPI_MB_GetSize(buffer));
    GetAdtsHeader(aac_header, u32SampleRate,
            u32ChnCnt, RK_MPI_MB_GetSize(buffer));
    char *adts_aac = (char *)malloc(RK_MPI_MB_GetSize(buffer) + sizeof(aac_header));

    memcpy(adts_aac, aac_header, 7);
    adts_aac += 7; //偏移 7 位来接收 aac 裸流数据
    memcpy(adts_aac, RK_MPI_MB_GetPtr(buffer), RK_MPI_MB_GetSize(buffer));
    adts_aac -= 7; //偏移回退 7 位来指向帧的头部数据

    if (g_audiortsplive && g_audiortsp_session) {
        rtsp_tx_audio(g_audiortsp_session, (unsigned char *)adts_aac, (RK_MPI_MB_GetSize(buffer) + 7),
                RK_MPI_MB_GetTimestamp(buffer));
        rtsp_do_event(g_audiortsplive);
    }

    RK_MPI_MB_ReleaseBuffer(buffer);
}
return NULL;

}

static void *VideoRtspStream(void data) {
printf(“--------------------------------Get packet-VideoRtspStream-----------------------------------\n”);
MEDIA_BUFFER buffer;
while(1) {
buffer = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, 0, -1);
if (!buffer)
continue;
printf(“#Get packet-VideoRtspStream, size %zu\n”, RK_MPI_MB_GetSize(buffer));
if (g_rtsplive && g_rtsp_session) {
rtsp_tx_video(g_rtsp_session, (unsigned char
)RK_MPI_MB_GetPtr(buffer), RK_MPI_MB_GetSize(buffer),
RK_MPI_MB_GetTimestamp(buffer));
rtsp_do_event(g_rtsplive);
}

    RK_MPI_MB_ReleaseBuffer(buffer);
}
return NULL;

}

camera_thread::camera_thread(QObject *parent)
{
ret = SAMPLE_COMM_ISP_Init(0, RK_AIQ_WORKING_MODE_NORMAL, RK_TRUE, iq_dir);
if (ret)
return ;
SAMPLE_COMM_ISP_Run(0);
SAMPLE_COMM_ISP_SET_Contrast(0, 110);//亮度
SAMPLE_COMM_ISP_SetFrameRate(0, 30);

RK_MPI_SYS_Init();
 memset(&vi_chn_attr, 0, sizeof(vi_chn_attr));
 vi_chn_attr.pcVideoNode = "rkispp_scale0";
 vi_chn_attr.u32BufCnt = 4;
 vi_chn_attr.u32Width = video_width;
 vi_chn_attr.u32Height = video_height;
 vi_chn_attr.enPixFmt = IMAGE_TYPE_NV12;
 vi_chn_attr.enWorkMode = VI_WORK_MODE_NORMAL;
 vi_chn_attr.enBufType = VI_CHN_BUF_TYPE_MMAP;
 ret = RK_MPI_VI_SetChnAttr(0, 0, &vi_chn_attr);
 ret |= RK_MPI_VI_EnableChn(0, 0);
 if (ret) {
  printf("Create vi[1] failed! ret=%d\n", ret);
  return ;
 }

 memset(&stRgaAttr, 0, sizeof(stRgaAttr));
 stRgaAttr.bEnBufPool = RK_TRUE;
 stRgaAttr.u16BufPoolCnt = 4;
 stRgaAttr.u16Rotaion = 0;
 stRgaAttr.u16Rotaion = 90;
 stRgaAttr.stImgIn.u32X = 0;
 stRgaAttr.stImgIn.u32Y = 0;
 stRgaAttr.stImgIn.imgType = IMAGE_TYPE_NV12;
 stRgaAttr.stImgIn.u32Width = video_width;
 stRgaAttr.stImgIn.u32Height = video_height;
 stRgaAttr.stImgIn.u32HorStride = video_width;
 stRgaAttr.stImgIn.u32VirStride = video_height;
 stRgaAttr.stImgOut.u32X = 0;
 stRgaAttr.stImgOut.u32Y = 0;
 stRgaAttr.stImgOut.imgType = IMAGE_TYPE_NV12;
 stRgaAttr.stImgOut.u32Width = disp_height;
 stRgaAttr.stImgOut.u32Height = disp_width;
 stRgaAttr.stImgOut.u32HorStride = disp_height;
 stRgaAttr.stImgOut.u32VirStride = disp_width;
 ret = RK_MPI_RGA_CreateChn(0, &stRgaAttr);
 if (ret) {
   printf("Create rga[0] falied! ret=%d\n", ret);
   return ;
 }

memset(&venc_chn_attr, 0, sizeof(venc_chn_attr));

    venc_chn_attr.stVencAttr.enType = RK_CODEC_TYPE_H264;
    venc_chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
    venc_chn_attr.stRcAttr.stH264Cbr.u32Gop = 30;
    venc_chn_attr.stRcAttr.stH264Cbr.u32BitRate = disp_width * disp_height;
    // frame rate: in 30/1, out 30/1.
    venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;
    venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 30;
    venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1;
    venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 30;

  venc_chn_attr.stVencAttr.imageType = IMAGE_TYPE_NV12;
  venc_chn_attr.stVencAttr.u32PicWidth = disp_height;
  venc_chn_attr.stVencAttr.u32PicHeight = disp_width;
  venc_chn_attr.stVencAttr.u32VirWidth = disp_height;
  venc_chn_attr.stVencAttr.u32VirHeight = disp_width;
  venc_chn_attr.stVencAttr.u32Profile = 77;
  ret = RK_MPI_VENC_CreateChn(0, &venc_chn_attr);
  if (ret) {
    printf("ERROR: create VENC[0] error! ret=%d\n", ret);
  }

stSrcChn.enModId = RK_ID_VI;
stSrcChn.s32DevId = 0;
stSrcChn.s32ChnId = 0;
stDestChn.enModId = RK_ID_RGA;
stDestChn.s32DevId = 0;
stDestChn.s32ChnId = 0;
ret = RK_MPI_SYS_Bind(&stSrcChn, &stDestChn);
if (ret) {
printf(“Bind vi[0] to rga[0] failed! ret=%d\n”, ret);
return;
}

 // MPP_CHN_S stEncChn;
  stSrcChn.enModId = RK_ID_RGA;
  stSrcChn.s32DevId = 0;
  stSrcChn.s32ChnId = 0;
  stDestChn.enModId = RK_ID_VENC;
  stDestChn.s32DevId = 0;
  stDestChn.s32ChnId = 0;
  ret = RK_MPI_SYS_Bind(&stSrcChn, &stDestChn);
  if (ret) {
    printf("ERROR: register output callback for VENC[0] error! ret=%d\n", ret);
  }

  g_rtsplive = create_rtsp_demo(8554);
  g_rtsp_session = rtsp_new_session(g_rtsplive, "/H264_stream_0");

// CODEC_TYPE_E enCodecType = session_cfg->VideoType;
rtsp_set_video(g_rtsp_session, RTSP_CODEC_ID_VIDEO_H264, NULL, 0);
rtsp_sync_video_ts(g_rtsp_session, rtsp_get_reltime(), rtsp_get_ntptime());

  g_audiortsplive = create_rtsp_demo(8555);
  g_audiortsp_session = rtsp_new_session(g_audiortsplive, "/audio_stream_0");
  rtsp_set_audio(g_audiortsp_session, RTSP_CODEC_ID_AUDIO_AAC, NULL, 0);
  rtsp_sync_audio_ts(g_audiortsp_session, rtsp_get_reltime(), rtsp_get_ntptime());
  pthread_create(&audio_rtsp_thread, NULL, AudioRtspStream, NULL);

}
大致是这样~参数有一些声明没贴。这个可以生成相应的视频音频流,我打算直接一起拉这个两个流一起播放,看了效果,没有明显的口型对不上的问题。