FFmpeg开发笔记(九):ffmpeg解码rtsp流并使用SDL同步播放

简介: FFmpeg开发笔记(九):ffmpeg解码rtsp流并使用SDL同步播放

前言

  ffmpeg播放rtsp网络流和摄像头流。


Demo

  使用ffmpeg播放局域网rtsp1080p海康摄像头,调整摄像头码流后:延迟0.2s,不存在马赛克

  

  使用ffmpeg播放局域网rtsp1080p海康摄像头:延迟0.2s,存在马赛克


  

  使用ffmpeg播放网络rtsp文件流:偶尔卡顿,延迟看不出

  

  使用vlc软件播放局域网rtsp1080p海康摄像头:演示2s,不存在马赛克

  

  使用vlc软件播放网络rtsp文件流:不卡顿,延迟看不出

  


FFmpeg基本播放流程

ffmpeg解码流程

  ffmpeg新增API的解码执行流程。

  新api解码基本流程如下:

  

步骤一:注册:

  使用ffmpeg对应的库,都需要进行注册,可以注册子项也可以注册全部。

步骤二:打开文件:

  打开文件,根据文件名信息获取对应的ffmpeg全局上下文。

步骤三:探测流信息:

  一定要探测流信息,拿到流编码的编码格式,不探测流信息则其流编码器拿到的编码类型可能为空,后续进行数据转换的时候就无法知晓原始格式,导致错误。

步骤四:查找对应的解码器

  依据流的格式查找解码器,软解码还是硬解码是在此处决定的,但是特别注意是否支持硬件,需要自己查找本地的硬件解码器对应的标识,并查询其是否支持。普遍操作是,枚举支持文件后缀解码的所有解码器进行查找,查找到了就是可以硬解了(此处,不做过多的讨论,对应硬解码后续会有文章进行进一步研究)。

  (注意:解码时查找解码器,编码时查找编码器,两者函数不同,不要弄错了,否则后续能打开但是数据是错的)

步骤五:打开解码器

  开打解码器的时候,播放的是rtsp流,需要设置一些参数,在ffmpeg中参数的设置是通过AVDictionary来设置的。

  使用以上设置的参数,传入并打开获取到的解码器。

AVDictionary *pAVDictionary = 0
// 设置缓存大小 1024000byte
av_dict_set(&pAVDictionary, "buffer_size", "1024000", 0);
// 设置超时时间 20s
av_dict_set(&pAVDictionary, "stimeout", "20000000", 0);
// 设置最大延时 3s
av_dict_set(&pAVDictionary, "max_delay", "30000000", 0);
// 设置打开方式 tcp/udp
av_dict_set(&pAVDictionary, "rtsp_transport", "tcp", 0);
ret = avcodec_open2(pAVCodecContext, pAVCodec, &pAVDictionary);
if(ret)
{
    LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
    return;
}

步骤六:申请缩放数据格式转换结构体

  此处特别注意,基本上解码的数据都是yuv系列格式,但是我们显示的数据是rgb等相关颜色空间的数据,所以此处转换结构体就是进行转换前到转换后的描述,给后续转换函数提供转码依据,是很关键并且非常常用的结构体。

步骤七:申请缓存区

  申请一个缓存区outBuffer,fill到我们目标帧数据的data上,比如rgb数据,QAVFrame的data上存是有指定格式的数据,且存储有规则,而fill到outBuffer(自己申请的目标格式一帧缓存区),则是我们需要的数据格式存储顺序。

  举个例子,解码转换后的数据为rgb888,实际直接用data数据是错误的,但是用outBuffer就是对的,所以此处应该是ffmpeg的fill函数做了一些转换。

进入循环解码:

步骤八:分组数据包送往解码器(此处由一个步骤变为了步骤八和步骤九)

  拿取封装的一个packet,判断packet数据的类型进行送往解码器解码。

步骤九:从解码器缓存中获取解码后的数据

  一个包可能存在多组数据,老的api获取的是第一个,新的api分开后,可以循环获取,直至获取不到跳转“步骤十二”。

步骤十一:自行处理

  拿到了原始数据自行处理。

  不断循环,直到拿取pakcet函数成功,但是无法got一帧数据,则代表文件解码已经完成。

  帧率需要自己控制循环,此处只是循环拿取,可加延迟等。

步骤十二:释放QAVPacket

  此处要单独列出是因为,其实很多网上和开发者的代码:

  在进入循环解码前进行了av_new_packet,循环中未av_free_packet,造成内存溢出;

  在进入循环解码前进行了av_new_packet,循环中进行av_free_pakcet,那么一次new对应无数次free,在编码器上是不符合前后一一对应规范的。

  查看源代码,其实可以发现av_read_frame时,自动进行了av_new_packet(),那么其实对于packet,只需要进行一次av_packet_alloc()即可,解码完后av_free_packet。

  执行完后,返回执行“步骤八:获取一帧packet”,一次循环结束。

步骤十三:释放转换结构体

  全部解码完成后,安装申请顺序,进行对应资源的释放。

步骤十四:关闭解码/编码器

  关闭之前打开的解码/编码器。

步骤十五:关闭上下文

  关闭文件上下文后,要对之前申请的变量按照申请的顺序,依次释放。


补充

  ffmpeg打开rtsp出现严重的马赛克和部分卡顿,需要修改文件udp.c的缓存区大小,修改后需要重新编译。

  实测更改后的马赛克会好一些,相比较软件来说有一些差距的,这部分需要继续优化。

  编译请参照《FFmpeg开发笔记(三):ffmpeg介绍、windows编译以及开发环境搭建


Demo源码

void FFmpegManager::testDecodeRtspSyncShow()
{
    QString rtspUrl = "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8";
//    QString rtspUrl = "rtsp://admin:Admin123@192.168.1.65:554/h264/ch1/main/av_stream";
    // SDL相关变量预先定义
    SDL_Window *pSDLWindow = 0;
    SDL_Renderer *pSDLRenderer = 0;
    SDL_Surface *pSDLSurface = 0;
    SDL_Texture *pSDLTexture = 0;
    SDL_Event event;
    qint64 startTime = 0;                           // 记录播放开始
    int currentFrame = 0;                           // 当前帧序号
    double fps = 0;                                 // 帧率
    double interval = 0;                            // 帧间隔
    // ffmpeg相关变量预先定义与分配
    AVFormatContext *pAVFormatContext = 0;          // ffmpeg的全局上下文,所有ffmpeg操作都需要
    AVStream *pAVStream = 0;                        // ffmpeg流信息
    AVCodecContext *pAVCodecContext = 0;            // ffmpeg编码上下文
    AVCodec *pAVCodec = 0;                          // ffmpeg编码器
    AVPacket *pAVPacket = 0;                        // ffmpag单帧数据包
    AVFrame *pAVFrame = 0;                          // ffmpeg单帧缓存
    AVFrame *pAVFrameRGB32 = 0;                     // ffmpeg单帧缓存转换颜色空间后的缓存
    struct SwsContext *pSwsContext = 0;             // ffmpeg编码数据格式转换
    AVDictionary *pAVDictionary = 0;                // ffmpeg数据字典,用于配置一些编码器属性等
    int ret = 0;                                    // 函数执行结果
    int videoIndex = -1;                            // 音频流所在的序号
    int numBytes = 0;                               // 解码后的数据长度
    uchar *outBuffer = 0;                           // 解码后的数据存放缓存区
    pAVFormatContext = avformat_alloc_context();    // 分配
    pAVPacket = av_packet_alloc();                  // 分配
    pAVFrame = av_frame_alloc();                    // 分配
    pAVFrameRGB32 = av_frame_alloc();               // 分配
    if(!pAVFormatContext || !pAVPacket || !pAVFrame || !pAVFrameRGB32)
    {
        LOG << "Failed to alloc";
        return;
    }
    // 步骤一:注册所有容器和编解码器(也可以只注册一类,如注册容器、注册编码器等)
    av_register_all();
    avformat_network_init();
    // 步骤二:打开文件(ffmpeg成功则返回0)
    LOG << "打开:" << rtspUrl;
    ret = avformat_open_input(&pAVFormatContext, rtspUrl.toUtf8().data(), 0, 0);
    if(ret)
    {
        LOG << "Failed";
        return;
    }
    // 步骤三:探测流媒体信息
    ret = avformat_find_stream_info(pAVFormatContext, 0);
    if(ret < 0)
    {
        LOG << "Failed to avformat_find_stream_info(pAVFormatContext, 0)";
        return;
    }
    // 步骤四:提取流信息,提取视频信息
    for(int index = 0; index < pAVFormatContext->nb_streams; index++)
    {
        pAVCodecContext = pAVFormatContext->streams[index]->codec;
        pAVStream = pAVFormatContext->streams[index];
        switch (pAVCodecContext->codec_type)
        {
        case AVMEDIA_TYPE_UNKNOWN:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_UNKNOWN";
            break;
        case AVMEDIA_TYPE_VIDEO:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_VIDEO";
            videoIndex = index;
            LOG;
            break;
        case AVMEDIA_TYPE_AUDIO:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_AUDIO";
            break;
        case AVMEDIA_TYPE_DATA:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_DATA";
            break;
        case AVMEDIA_TYPE_SUBTITLE:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_SUBTITLE";
            break;
        case AVMEDIA_TYPE_ATTACHMENT:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_ATTACHMENT";
            break;
        case AVMEDIA_TYPE_NB:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_NB";
            break;
        default:
            break;
        }
        // 已经找打视频品流
        if(videoIndex != -1)
        {
            break;
        }
    }
    if(videoIndex == -1 || !pAVCodecContext)
    {
        LOG << "Failed to find video stream";
        return;
    }
    // 步骤五:对找到的视频流寻解码器
    pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
    if(!pAVCodec)
    {
        LOG << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"
            << pAVCodecContext->codec_id;
        return;
    }
    // 步骤六:打开解码器
    // 设置缓存大小 1024000byte
    av_dict_set(&pAVDictionary, "buffer_size", "1024000", 0);
    // 设置超时时间 20s
    av_dict_set(&pAVDictionary, "stimeout", "20000000", 0);
    // 设置最大延时 3s
    av_dict_set(&pAVDictionary, "max_delay", "30000000", 0);
    // 设置打开方式 tcp/udp
    av_dict_set(&pAVDictionary, "rtsp_transport", "tcp", 0);
    ret = avcodec_open2(pAVCodecContext, pAVCodec, &pAVDictionary);
    if(ret)
    {
        LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
        return;
    }
    // 显示视频相关的参数信息(编码上下文)
    LOG << "比特率:" << pAVCodecContext->bit_rate;
    LOG << "宽高:" << pAVCodecContext->width << "x" << pAVCodecContext->height;
    LOG << "格式:" << pAVCodecContext->pix_fmt;  // AV_PIX_FMT_YUV420P 0
    LOG << "帧率分母:" << pAVCodecContext->time_base.den;
    LOG << "帧率分子:" << pAVCodecContext->time_base.num;
    LOG << "帧率分母:" << pAVStream->avg_frame_rate.den;
    LOG << "帧率分子:" << pAVStream->avg_frame_rate.num;
    LOG << "总时长:" << pAVStream->duration / 10000.0 << "s";
    LOG << "总帧数:" << pAVStream->nb_frames;
    // 有总时长的时候计算帧率(较为准确)
//    fps = pAVStream->nb_frames / (pAVStream->duration / 10000.0);
//    interval = pAVStream->duration / 10.0 / pAVStream->nb_frames;
    // 没有总时长的时候,使用分子和分母计算
    fps = pAVStream->avg_frame_rate.num * 1.0f / pAVStream->avg_frame_rate.den;
    interval = 1 * 1000 / fps;
    LOG << "平均帧率:" << fps;
    LOG << "帧间隔:" << interval << "ms";
    // 步骤七:对拿到的原始数据格式进行缩放转换为指定的格式高宽大小
    pSwsContext = sws_getContext(pAVCodecContext->width,
                                 pAVCodecContext->height,
                                 pAVCodecContext->pix_fmt,
                                 pAVCodecContext->width,
                                 pAVCodecContext->height,
                                 AV_PIX_FMT_RGBA,
                                 SWS_FAST_BILINEAR,
                                 0,
                                 0,
                                 0);
    numBytes = avpicture_get_size(AV_PIX_FMT_RGBA,
                                  pAVCodecContext->width,
                                  pAVCodecContext->height);
    outBuffer = (uchar *)av_malloc(numBytes);
    // pAVFrame32的data指针指向了outBuffer
    avpicture_fill((AVPicture *)pAVFrameRGB32,
                   outBuffer,
                   AV_PIX_FMT_RGBA,
                   pAVCodecContext->width,
                   pAVCodecContext->height);
    ret = SDL_Init(SDL_INIT_VIDEO);
    if(ret)
    {
        LOG << "Failed";
        return;
    }
    pSDLWindow = SDL_CreateWindow(rtspUrl.toUtf8().data(),
                                  0,
                                  0,
                                  pAVCodecContext->width,
                                  pAVCodecContext->height,
                                  SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if(!pSDLWindow)
    {
        LOG << "Failed";
        return;
    }
    pSDLRenderer = SDL_CreateRenderer(pSDLWindow, -1, 0);
    if(!pSDLRenderer)
    {
        LOG << "Failed";
        return;
    }
    startTime = QDateTime::currentDateTime().toMSecsSinceEpoch();
    currentFrame = 0;
    pSDLTexture = SDL_CreateTexture(pSDLRenderer,
//                                  SDL_PIXELFORMAT_IYUV,
                                    SDL_PIXELFORMAT_YV12,
                                    SDL_TEXTUREACCESS_STREAMING,
                                    pAVCodecContext->width,
                                    pAVCodecContext->height);
    if(!pSDLTexture)
    {
        LOG << "Failed";
        return;
    }
    // 步骤八:读取一帧数据的数据包
    while(av_read_frame(pAVFormatContext, pAVPacket) >= 0)
    {
        if(pAVPacket->stream_index == videoIndex)
        {
            // 步骤八:对读取的数据包进行解码
            ret = avcodec_send_packet(pAVCodecContext, pAVPacket);
            if(ret)
            {
                LOG << "Failed to avcodec_send_packet(pAVCodecContext, pAVPacket) ,ret =" << ret;
                break;
            }
            while(!avcodec_receive_frame(pAVCodecContext, pAVFrame))
            {
                sws_scale(pSwsContext,
                          (const uint8_t * const *)pAVFrame->data,
                          pAVFrame->linesize,
                          0,
                          pAVCodecContext->height,
                          pAVFrameRGB32->data,
                          pAVFrameRGB32->linesize);
                // 格式为RGBA=8:8:8:8”
                // rmask 应为 0xFF000000  但是颜色不对 改为 0x000000FF 对了
                // gmask     0x00FF0000                  0x0000FF00
                // bmask     0x0000FF00                  0x00FF0000
                // amask     0x000000FF                  0xFF000000
                // 测试了ARGB,也是相反的,而QImage是可以正确加载的
                // 暂时只能说这个地方标记下,可能有什么设置不对什么的
                qDebug() << __FILE__ << __LINE__  << pSDLTexture;
                SDL_UpdateYUVTexture(pSDLTexture,
                                     NULL,
                                     pAVFrame->data[0], pAVFrame->linesize[0],
                                     pAVFrame->data[1], pAVFrame->linesize[1],
                                     pAVFrame->data[2], pAVFrame->linesize[2]);
                qDebug() << __FILE__ << __LINE__  << pSDLTexture;
                SDL_RenderClear(pSDLRenderer);
                // Texture复制到Renderer
                SDL_Rect        sdlRect;
                sdlRect.x = 0;
                sdlRect.y = 0;
                sdlRect.w = pAVFrame->width;
                sdlRect.h = pAVFrame->height;
                qDebug() << __FILE__ << __LINE__ << SDL_RenderCopy(pSDLRenderer, pSDLTexture, 0, &sdlRect) << pSDLTexture;
                // 更新Renderer显示
                SDL_RenderPresent(pSDLRenderer);
                // 事件处理
                SDL_PollEvent(&event);
            }
            // 下一帧
            currentFrame++;
            while(QDateTime::currentDateTime().toMSecsSinceEpoch() - startTime < currentFrame * interval)
            {
                SDL_Delay(1);
            }
            LOG << "current:" << currentFrame <<"," << time << (QDateTime::currentDateTime().toMSecsSinceEpoch() - startTime);
        }
    }
    LOG << "释放回收资源";
    if(outBuffer)
    {
        av_free(outBuffer);
        outBuffer = 0;
    }
    if(pSwsContext)
    {
        sws_freeContext(pSwsContext);
        pSwsContext = 0;
        LOG << "sws_freeContext(pSwsContext)";
    }
    if(pAVFrameRGB32)
    {
        av_frame_free(&pAVFrameRGB32);
        pAVFrame = 0;
        LOG << "av_frame_free(pAVFrameRGB888)";
    }
    if(pAVFrame)
    {
        av_frame_free(&pAVFrame);
        pAVFrame = 0;
        LOG << "av_frame_free(pAVFrame)";
    }
    if(pAVPacket)
    {
        av_free_packet(pAVPacket);
        pAVPacket = 0;
        LOG << "av_free_packet(pAVPacket)";
    }
    if(pAVCodecContext)
    {
        avcodec_close(pAVCodecContext);
        pAVCodecContext = 0;
        LOG << "avcodec_close(pAVCodecContext);";
    }
    if(pAVFormatContext)
    {
        avformat_close_input(&pAVFormatContext);
        avformat_free_context(pAVFormatContext);
        pAVFormatContext = 0;
        LOG << "avformat_free_context(pAVFormatContext)";
    }
    // 步骤五:销毁渲染器
    SDL_DestroyRenderer(pSDLRenderer);
    // 步骤六:销毁窗口
    SDL_DestroyWindow(pSDLWindow);
    // 步骤七:退出SDL
    SDL_Quit();
}


工程模板v1.5.0

  对应工程模板v1.5.0:增加播放rtsp使用SDL播放Demo

相关文章
|
13天前
|
XML Java Android开发
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
GSYVideoPlayer是一款国产移动端视频播放器,支持弹幕、滤镜、广告等功能,采用IJKPlayer、Media3(EXOPlayer)、MediaPlayer及AliPlayer多种内核。截至2024年8月,其GitHub星标数达2万。集成时需使用新版Android Studio,并按特定步骤配置依赖与权限。提供了NormalGSYVideoPlayer、GSYADVideoPlayer及ListGSYVideoPlayer三种控件,支持HLS、RTMP等多种直播链接。
51 18
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
|
12天前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
EasyPusher是一款国产RTSP直播录制推流客户端工具,支持Windows、Linux、Android及iOS等系统。尽管其GitHub仓库(安卓版:https://github.com/EasyDarwin/EasyPusher-Android)已多年未更新,但通过一系列改造,如升级SDK版本、迁移到AndroidX、指定本地NDK版本及更新Gradle版本等,仍可在最新Android Studio上运行。以下是针对Android Studio Dolphin版本的具体改造步骤。
38 3
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
|
11天前
|
编解码 API 数据安全/隐私保护
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
【9月更文挑战第21天】本文介绍了如何使用FFmpeg和EasyPusher实现移动端RTSP直播。首先概述了EasyPusher的功能及其API,接着详细描述了安装FFmpeg、获取EasyPusher库、初始化对象、打开输入流、配置推送参数及读取推送帧的具体步骤,并提醒开发者注意网络环境、编码参数和权限管理等问题,以确保直播质量与稳定性。
|
25天前
|
Android开发 计算机视觉 C++
FFmpeg开发笔记(五十一)适合学习研究的几个音视频开源框架
音视频编程对许多程序员来说是一片充满挑战的领域,但借助如OpenCV、LearnOpenGL、FFmpeg、OBS Studio及VLC media player等强大的开源工具,可以降低入门门槛。这些框架不仅覆盖了计算机视觉、图形渲染,还包括多媒体处理与直播技术,通过多种编程语言如Python、C++的应用,使得音视频开发更为便捷。例如,OpenCV支持跨平台的视觉应用开发,FFmpeg则擅长多媒体文件的处理与转换,而VLC media player则是验证音视频文件质量的有效工具。
44 0
FFmpeg开发笔记(五十一)适合学习研究的几个音视频开源框架
|
27天前
|
编解码 移动开发 安全
FFmpeg开发笔记(五十)聊聊几种流媒体传输技术的前世今生
自互联网普及以来,流媒体技术特别是视频直播技术不断进步,出现了多种传输协议。早期的MMS由微软主导,但随WMV格式衰落而减少使用。RTSP由网景和RealNetworks联合提出,支持多种格式,但在某些现代应用中不再受支持。RTMP由Adobe开发,曾广泛用于网络直播,但因HTML5不支持Flash而受影响。HLS由苹果开发,基于HTTP,适用于点播。SRT和RIST均为较新协议,强调安全与可靠性,尤其SRT在电视直播中应用增多。尽管RTMP仍占一定市场,但SRT等新协议正逐渐兴起。
67 8
FFmpeg开发笔记(五十)聊聊几种流媒体传输技术的前世今生
|
1月前
|
Web App开发 Java 视频直播
FFmpeg开发笔记(四十九)助您在毕业设计中脱颖而出的几个流行APP
对于软件、计算机等专业的毕业生,毕业设计需实现实用软件或APP。新颖的设计应结合最新技术,如5G时代的音视频技术。示例包括: 1. **短视频分享APP**: 集成FFmpeg实现视频剪辑功能,如添加字幕、转场特效等。 2. **电商购物APP**: 具备直播带货功能,使用RTMP/SRT协议支持流畅直播体验。 3. **同城生活APP**: 引入WebRTC技术实现可信的视频通话功能。这些应用不仅实用,还能展示开发者紧跟技术潮流的能力。
68 4
FFmpeg开发笔记(四十九)助您在毕业设计中脱颖而出的几个流行APP
|
2月前
|
JavaScript 前端开发 Java
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
IT寒冬使APP开发门槛提升,安卓程序员需转型。选项包括:深化Android开发,跟进Google新技术如Kotlin、Jetpack、Flutter及Compose;研究Android底层框架,掌握AOSP;转型Java后端开发,学习Spring Boot等框架;拓展大前端技能,掌握JavaScript、Node.js、Vue.js及特定框架如微信小程序、HarmonyOS;或转向C/C++底层开发,通过音视频项目如FFmpeg积累经验。每条路径都有相应的书籍和技术栈推荐,助你顺利过渡。
45 3
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
|
2月前
|
Web App开发 Android开发
FFmpeg开发笔记(四十六)利用SRT协议构建手机APP的直播Demo
实时数据传输在互联网中至关重要,不仅支持即时通讯如QQ、微信的文字与图片传输,还包括音视频通信。一对一通信常采用WebRTC技术,如《Android Studio开发实战》中的App集成示例;而一对多的在线直播则需部署独立的流媒体服务器,使用如SRT等协议。SRT因其优越的直播质量正逐渐成为主流。本文档概述了SRT协议的使用,包括通过OBS Studio和SRT Streamer进行SRT直播推流的方法,并展示了推流与拉流的成功实例。更多细节参见《FFmpeg开发实战》一书。
43 1
FFmpeg开发笔记(四十六)利用SRT协议构建手机APP的直播Demo
|
2月前
|
Web App开发 5G Linux
FFmpeg开发笔记(四十四)毕业设计可做的几个拉满颜值的音视频APP
一年一度的毕业季来临,计算机专业的毕业设计尤为重要,不仅关乎学业评价还积累实战经验。选择紧跟5G技术趋势的音视频APP作为课题极具吸引力。这里推荐三类应用:一是融合WebRTC技术实现视频通话的即时通信APP;二是具备在线直播功能的短视频分享平台,涉及RTMP/SRT等直播技术;三是具有自定义动画特效及卡拉OK歌词字幕功能的视频剪辑工具。这些项目不仅技术含量高,也符合市场需求,是毕业设计的理想选择。
66 6
FFmpeg开发笔记(四十四)毕业设计可做的几个拉满颜值的音视频APP
|
2月前
|
编解码 Java Android开发
FFmpeg开发笔记(四十五)使用SRT Streamer开启APP直播推流
​SRT Streamer是一个安卓手机端的开源SRT协议直播推流框架,可用于RTMP直播和SRT直播。SRT Streamer支持的视频编码包括H264、H265等等,支持的音频编码包括AAC、OPUS等等,可谓功能强大的APP直播框架。另一款APP直播框架RTMP Streamer支持RTMP直播和RTSP直播,不支持SRT协议的直播。而本文讲述的SRT Streamer支持RTMP直播和SRT直播,不支持RTSP协议的直播。有关RTMP Streamer的说明参见之前的文章《使用RTMP Streamer开启APP直播推流》,下面介绍如何使用SRT Streamer开启手机直播。
56 4
FFmpeg开发笔记(四十五)使用SRT Streamer开启APP直播推流