FFmpeg开发笔记(五):ffmpeg解码的基本流程详解(ffmpeg3新解码api)

简介: FFmpeg开发笔记(五):ffmpeg解码的基本流程详解(ffmpeg3新解码api)

前言

  ffmpeg涉及了很多,循序渐进,本篇描述基本的解码流程,主要ffmpeg解码流程在ffmpeg3及以后新增的2个api进行了补充,更为详细可以参考《FFmpeg开发笔记(四):ffmpeg解码的基本流程详解


Demo

  


ffmpeg解码流程

  ffmpeg的解码和编码都遵循其基本的执行流程。

  新api解码基本本流程如下:

  

  以下是老版本api解码流程:

  

步骤一:注册:

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

步骤二:打开文件:

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

步骤三:探测流信息:

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

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

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

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

步骤五:打开解码器

  打开获取到的解码器。

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

  此处特别注意,基本上解码的数据都是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解码相关变量

AVFormatContext

  AVFormatContext描述了一个媒体文件或媒体流的构成和基本信息,位于avformat.h文件中。

AVInputFormat

  AVInputFormat 是类似COM 接口的数据结构,表示输入文件容器格式,着重于功能函数,一种文件容器格式对应一个AVInputFormat 结构,在程序运行时有多个实例,位于avoformat.h文件中。

AVDictionary

  AVDictionary 是一个字典集合,键值对,用于配置相关信息。

AVCodecContext

  AVCodecContext是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息,位于avcodec.h文件中。

AVPacket

  AVPacket是FFmpeg中很重要的一个数据结构,它保存了解复用(demuxer)之后,解码(decode)之前的数据(仍然是压缩后的数据)和关于这些数据的一些附加的信息,如显示时间戳(pts),解码时间戳(dts),数据时长(duration),所在流媒体的索引(stream_index)等等。

  使用前,使用av_packet_alloc()分配,

AVCodec

  AVCodec是存储编解码器信息的结构体,位于avcodec.h文件中。

AVFrame

  AVFrame中存储的是经过解码后的原始数据。在解码中,AVFrame是解码器的输出;在编码中,AVFrame是编码器的输入。

  使用前,使用av_frame_alloc()进行分配。

struct SwsContext

  使用前,使用sws_getContext()进行获取,主要用于视频图像的转换。


本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108639103

ffmpeg解码流程相关函数原型

av_register_all

void av_register_all(void);

  初始化libavformat并注册所有muxer、demuxer和协议。如果不调用此函数,则可以选择想要指定注册支持的哪种格式,通过av_register_input_format()、av_register_output_format()。

avformat_open_input

int avformat_open_input(AVFormatContext **ps,
                        const char *url,
                        AVInputFormat *fmt, 
                        AVDictionary **options);

  打开输入流并读取标头。编解码器未打开。流必须使用avformat_close_input()关闭,返回0-成功,<0-失败错误码。

  • 参数一:指向用户提供的AVFormatContext(由avformat_alloc_context分配)的指针。
  • 参数二:要打开的流的url
  • 参数三:fmt如果非空,则此参数强制使用特定的输入格式。否则将自动检测格式。
  • 参数四:包含AVFormatContext和demuxer私有选项的字典。返回时,此参数将被销毁并替换为包含找不到的选项。都有效则返回为空。

avformat_find_stream_info

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

读取检查媒体文件的数据包以获取具体的流信息,如媒体存入的编码格式。

  • 参数一:媒体文件上下文。
  • 参数二:字典,一些配置选项。

avcodec_find_decoder

AVCodec *avcodec_find_decoder(enum AVCodecID id);

  查找具有匹配编解码器ID的已注册解码器,解码时,已经获取到了,注册的解码器可以通过枚举查看,枚举太多,略。

avcodec_open2

int avcodec_open2(AVCodecContext *avctx, 
                  const AVCodec *codec, 
                  AVDictionary **options);

  初始化AVCodeContext以使用给定的AVCodec。

sws_getContext

struct SwsContext *sws_getContext(int srcW, 
                                  int srcH, 
                                  enum AVPixelFormat srcFormat,
                                  int dstW,
                                  int dstH, 
                                  enum AVPixelFormat dstFormat,
                                  int flags, SwsFilter *srcFilter,
                                  SwsFilter *dstFilter,
                                  const double *param);

  分配并返回一个SwsContext。需要它来执行sws_scale()进行缩放/转换操作。

avpicture_get_size

int avpicture_get_size(enum AVPixelFormat pix_fmt, int width, int height);

  返回存储具有给定参数的图像的缓存区域大小。

  • 参数一:图像的像素格式
  • 参数二:图像的像素宽度
  • 参数三:图像的像素高度

avpicture_fill

int avpicture_fill(AVPicture *picture,
              const uint8_t *ptr,
              enum AVPixelFormat pix_fmt,
              int width,
              int height);

  根据指定的图像、提供的数组设置数据指针和线条大小参数。

  • 参数一:输入AVFrame指针,强制转换为AVPciture即可。
  • 参数二:映射到的缓存区,开发者自己申请的存放图像数据的缓存区。
  • 参数三:图像数据的编码格式。
  • 参数四:图像像素宽度。
  • 参数五:图像像素高度。

av_read_frame

int av_read_frame(AVFormatContext *s, AVPacket *pkt);

  返回流的下一帧。此函数返回存储在文件中的内容,不对有效的帧进行验证。获取存储在文件中的帧中,并为每个调用返回一个。不会的省略有效帧之间的无效数据,以便给解码器最大可用于解码的信息。

  返回0是成功,小于0则是错误,大于0则是文件末尾,所以大于等于0是返回成功。

avcodec_decode_video2:老解码api

int avcodec_decode_video2(AVCodecContext *avctx,
                          AVFrame *picture,
                          int *got_picture_ptr,
                          const AVPacket *avpkt);

  将大小为avpkt->size from avpkt->data的视频帧解码为图片。一些解码器可以支持单个avpkg包中的多个帧,解码器将只解码第一帧。出错时返回负值,否则返回字节数,如果没有帧可以解压缩,则为0。

  • 参数一:编解码器上下文。
  • 参数二:将解码视频帧存储在AVFrame中。
  • 参数三:输入缓冲区的AVPacket。
  • 参数四:如果没有帧可以解压,那么得到的图片是0,否则,它是非零的。

avcodec_send_packet:ffmpeg3新增解码发送数据包给解码器

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

  将原始分组数据发送给解码器。

  在内部,此调用将复制相关的AVCodeContext字段,这些字段可以影响每个数据包的解码,并在实际解码数据包时应用这些字段。(例如AVCodeContext.skip_frame,这可能会指示解码器丢弃使用此函数发送的数据包所包含的帧。)

这个函数可以理解为ffmpeg为多线程准备的,将解码数据帧包送入编码器理解为一个线程,将从编码器获取解码后的数据理解为一个线程。

  • 参数一:编解码器上下文
  • 参数二:avpkt输入的AVPacket。通常,这将是一个单一的视频帧,或几个完整的音频帧。数据包的所有权归调用者所有,解码器不会写入数据包。解码器可以创建对分组数据的引用(如果分组没有被引用计数,则复制它)。与旧的API不同,数据包总是被完全消耗掉,如果它包含多个帧(例如某些音频编解码器),则需要在发送新数据包之前多次调用avcodec_receive_frame()。它可以是NULL(或者数据设置为NULL且大小设置为0的AVPacket);在这种情况下,它被认为是一个刷新包,它发出流结束的信号。发送第一个刷新包将返回成功。后续的是不必要的,将返回AVERROR ou EOF。如果解码器仍有帧缓冲,它将在发送刷新包后返回它们。

avcodec_receive_frame:ffmpeg3新增解码从解码器获取解码后的帧

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

  从解码器返回解码输出数据。这个函数可以理解为ffmpeg为多线程准备的,将解码数据帧包送入编码器理解为一个线程,将从编码器获取解码后的数据理解为一个线程。

  • 参数一:编解码器上下文
  • 参数二:这将被设置为参考计数的视频或音频解码器分配的帧(取决于解码器类型)。请注意,函数在执行任何其他操作之前总是调用av_frame_unref(frame)。初始化libavformat并注册所有muxer、demuxer和协议。如果不调用此函数,则可以选择想要指定注册支持的哪种格式,av_register_input_format()、av_register_output_format()。

avcodec_decode_video2

int avcodec_decode_video2(AVCodecContext *avctx,
                          AVFrame *picture,
                          int *got_picture_ptr,
                          const AVPacket *avpkt);

  将大小为avpkt->size from avpkt->data的视频帧解码为图片。一些解码器可以支持单个avpkg包中的多个帧,解码器将只解码第一帧。出错时返回负值,否则返回字节数,如果没有帧可以解压缩,则为0。

  • 参数一:编解码器上下文。
  • 参数二:将解码视频帧存储在AVFrame中。
  • 参数三:输入缓冲区的AVPacket。
  • 参数四:如果没有帧可以解压,那么得到的图片是0,否则,它是非零的。

sws_scale

int sws_scale(struct SwsContext *c,
              const uint8_t *const srcSlice[],
              const int srcStride[],
              int srcSliceY,
              int srcSliceH,
              uint8_t *const dst[],
              const int dstStride[]);

  在srcSlice中缩放图像切片并将结果缩放在dst中切片图像。切片是连续的序列图像中的行。

  • 参数一:以前用创建的缩放上下文*sws_getContext()。
  • 参数二:包含指向源片段,就是AVFrame的data。
  • 参数三:包含每个平面的跨步的数组,其实就是AVFrame的linesize。
  • 参数四:切片在源图像中的位置,从开始计数0对应切片第一行的图像,所以直接填0即可。
  • 参数五:源切片的像素高度。
  • 参数六:目标数据地址映像,是目标AVFrame的data。
  • 参数七:目标每个平面的跨步的数组,就是linesize。

av_free_packet

void av_free_packet(AVPacket *pkt);

  释放一个包。

avcodec_close

int avcodec_close(AVCodecContext *avctx);

  关闭给定的avcodeContext并释放与之关联的所有数据(但不是AVCodecContext本身)。

avformat_close_input

void avformat_close_input(AVFormatContext **s);

  关闭打开的输入AVFormatContext。释放它和它的所有内容并将*s设置为空。


Demo源码

void FFmpegManager::testDecodeNewApi()
{
    int frameIndex = 0;
//    QString fileName = "test/1.avi";
    QString fileName = "test/1.mp4";
    // ffmpeg相关变量预先定义与分配
    AVFormatContext *pAVFormatContext = 0;          // ffmpeg的全局上下文,所有ffmpeg操作都需要
//    AVInputFormat *pAVInputFormat = 0;              // ffmpeg的输入格式结构体
    AVDictionary *pAVDictionary = 0;                // ffmpeg的字典option,各种参数给格式编解码配置参数的
    AVCodecContext *pAVCodecContext = 0;            // ffmpeg编码上下文
    AVCodec *pAVCodec = 0;                          // ffmpeg编码器
    AVPacket *pAVPacket = 0;                        // ffmpag单帧数据包
    AVFrame *pAVFrame = 0;                          // ffmpeg单帧缓存
    AVFrame *pAVFrameRGB32 = 0;                     // ffmpeg单帧缓存转换颜色空间后的缓存
    struct SwsContext *pSwsContext = 0;             // ffmpag编码数据格式转换
    int ret = 0;                                    // 函数执行结果
    int videoIndex = -1;                            // 音频流所在的序号
    int gotPicture = 0;                             // 解码时数据是否解码成功
    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";
        goto END;
    }
    // 步骤一:注册所有容器和编解码器(也可以只注册一类,如注册容器、注册编码器等)
    av_register_all();
    // 步骤二:打开文件(ffmpeg成功则返回0)
    LOG << "文件:" << fileName << ",是否存在:" << QFile::exists(fileName);
//    ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), pAVInputFormat, 0);
    ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), 0, 0);
    if(ret)
    {
        LOG << "Failed";
        goto END;
    }
    // 步骤三:探测流媒体信息
    // Assertion desc failed at libswscale/swscale_internal.h:668
    // 入坑:因为pix_fmt为空,需要对编码器上下文进一步探测
    ret = avformat_find_stream_info(pAVFormatContext, 0);
    if(ret < 0)
    {
        LOG << "Failed to avformat_find_stream_info(pAVCodecContext, 0)";
        goto END;
    }
    // 打印文件信息
    LOG << "视频文件包含流信息的数量:" << pAVFormatContext->nb_streams;
    // 在Qt中av_dump_format不会进行命令行输出
//    av_dump_format(pAVFormatContext, 1, fileName.toUtf8().data(), 0);
    // 步骤三:提取流信息,提取视频信息
    for(int index = 0; index < pAVFormatContext->nb_streams; index++)
    {
        pAVCodecContext = pAVFormatContext->streams[index]->codec;
        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";
        goto END;
    }
    // 步骤四:对找到的视频流寻解码器
    pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
    if(!pAVCodec)
    {
        LOG << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"
            << pAVCodecContext->codec_id;
        goto END;
    }
    // 步骤五:打开解码器
    ret = avcodec_open2(pAVCodecContext, pAVCodec, NULL);
    if(ret)
    {
        LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
        goto END;
    }
    LOG << pAVCodecContext->width << "x" << pAVCodecContext->height;
    // 步骤六:对拿到的原始数据格式进行缩放转换为指定的格式高宽大小
    // Assertion desc failed at libswscale/swscale_internal.h:668
    // 入坑:因为pix_fmt为空,需要对编码器上下文进一步探测
    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);
    // 此处无需分配
    // av_read_frame时他会分配,av_new_packet多此一举,正好解释了一次new和多次free的问题
//    av_new_packet(pAVPacket, pAVCodecContext->width * pAVCodecContext->height);
    // 步骤七:读取一帧数据的数据包
    while(av_read_frame(pAVFormatContext, pAVPacket) >= 0)
    {
        if(pAVPacket->stream_index == videoIndex)
        {
#if 0
            // 步骤八:对读取的数据包进行解码
            ret = avcodec_decode_video2(pAVCodecContext, pAVFrame, &gotPicture, pAVPacket);
            if(ret < 0)
            {
                LOG << "Failed to avcodec_decode_video2(pAVFormatContext, pAVFrame, &gotPicture, pAVPacket)";
                break;
            }
            // 等于0代表拿到了解码的帧数据
            if(!gotPicture)
            {
                LOG << "no data";
                break;
            }else{
                sws_scale(pSwsContext,
                          (const uint8_t * const *)pAVFrame->data,
                          pAVFrame->linesize,
                          0,
                          pAVCodecContext->height,
                          pAVFrameRGB32->data,
                          pAVFrameRGB32->linesize);
                QImage imageTemp((uchar *)outBuffer,
                                 pAVCodecContext->width,
                                 pAVCodecContext->height,
                                 QImage::Format_RGBA8888);
                QImage image = imageTemp.copy();
                LOG << image.save(QString("%1.jpg").arg(frameIndex++));
            }
            av_free_packet(pAVPacket);
#else
            // 步骤八:发送数据给编码器
            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);
                QImage imageTemp((uchar *)outBuffer,
                                 pAVCodecContext->width,
                                 pAVCodecContext->height,
                                 QImage::Format_RGBA8888);
                QImage image = imageTemp.copy();
                LOG << image.save(QString("%1.jpg").arg(frameIndex++));
            }
            av_free_packet(pAVPacket);
#endif
        }
        QThread::msleep(1);
    }
END:
    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)";
    }
}


工程模板v1.1.1

  对应工程模板v1.1.1:新增ffmpeg3的api解码demo。


相关文章
|
6天前
|
安全 Linux Android开发
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
该文介绍了如何在Linux服务器上交叉编译Android的FFmpeg库以支持HTTPS视频播放。首先,从GitHub下载openssl源码,解压后通过编译脚本`build_openssl.sh`生成64位静态库。接着,更新环境变量加载openssl,并编辑FFmpeg配置脚本`config_ffmpeg_openssl.sh`启用openssl支持。然后,编译安装FFmpeg。最后,将编译好的库文件导入App工程的相应目录,修改视频链接为HTTPS,App即可播放HTTPS在线视频。
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
|
7天前
|
Web App开发 Windows
FFmpeg开发笔记(十五)详解MediaMTX的推拉流
MediaMTX是开源轻量级流媒体服务器,提供RTSP, RTMP, HLS, WebRTC和SRT服务。启动后,它在不同端口监听。通过FFmpeg的推拉流测试,证明了MediaMTX成功实现HLS流媒体转发,但HLS播放兼容性问题可能因缺少音频流导致。推流地址为rtsp://127.0.0.1:8554/stream,RTMP地址为rtmp://127.0.0.1:1935/stream,HLS播放地址为http://127.0.0.1:8888/stream(Chrome)和http://127.0.0.1:8888/stream/index.m3u8(其他播放器可能不支持)。
FFmpeg开发笔记(十五)详解MediaMTX的推拉流
|
10天前
|
缓存 编解码
FFmpeg开发笔记(十四)FFmpeg音频重采样的缓存
FFmpeg在视频流重编码和音频重采样中使用缓存机制。在音频文件格式转换时,特别是对于帧长度不固定的格式如ogg、amr、wma,需处理重采样缓存。通过调用`swr_convert`,传入空输入和0大小来清空缓存。在`swrmp3.c`中,修改帧样本数处理,并在循环结束后添加代码以冲刷缓存。编译并运行程序,将ogg文件重采样为MP3,日志显示操作成功,播放转换后的文件确认功能正常。
FFmpeg开发笔记(十四)FFmpeg音频重采样的缓存
|
13天前
|
编解码 Linux Windows
FFmpeg开发笔记(十三)Windows环境给FFmpeg集成libopus和libvpx
本文档介绍了在Windows环境下如何为FFmpeg集成libopus和libvpx库。首先,详细阐述了安装libopus的步骤,包括下载源码、配置、编译和安装,并更新环境变量。接着,同样详细说明了libvpx的安装过程,注意需启用--enable-pic选项以避免编译错误。最后,介绍了重新配置并编译FFmpeg以启用这两个库,通过`ffmpeg -version`检查是否成功集成。整个过程参照了《FFmpeg开发实战:从零基础到短视频上线》一书的相关章节。
FFmpeg开发笔记(十三)Windows环境给FFmpeg集成libopus和libvpx
|
JavaScript Java Serverless
入门 | 云开发平台1分钟开发一个API
云开发系列课程主要介绍了从入门到精通快速上手Serverless和云开发技术。学习内容涵盖云开发协同、云函数、云数据库、多媒体托管、前后端一体化框架等Serverless Web开发必备知识。希望通过云开发系列课程的学习与实际操作,让大家深入了解Serverless和云开发技术,并加深对阿里云云开发平台和阿里云Serverless产品的理解与认识。 本篇内容作为入门知识,让你在一分钟之内运行起一个Java/NodeJS/Python/PHP任何一门语言的Serverless API ,让你可以在后续的课程中向API添加各种有意思的功能。
入门 | 云开发平台1分钟开发一个API
|
29天前
|
SQL API Python
Python DB API下规范下cursor对象常用接口
Python DB API下规范下cursor对象常用接口。
21 4
|
4天前
|
JSON 搜索推荐 数据挖掘
电商数据分析的利器:电商关键词搜索API接口(标题丨图片丨价格丨链接)
淘宝关键词搜索接口为电商领域的数据分析提供了丰富的数据源。通过有效利用这一接口,企业和研究人员可以更深入地洞察市场动态,优化营销策略,并提升用户体验。随着电商平台技术的不断进步,未来的API将更加智能和个性化,为电商行业带来更多的可能性。
|
12天前
|
存储 缓存 运维
DataWorks操作报错合集之DataWorks根据api,调用查询文件列表接口报错如何解决
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
23 1
|
12天前
|
SQL 数据管理 API
数据管理DMS产品使用合集之阿里云DMS提供API接口来进行数据导出功能吗
阿里云数据管理DMS提供了全面的数据管理、数据库运维、数据安全、数据迁移与同步等功能,助力企业高效、安全地进行数据库管理和运维工作。以下是DMS产品使用合集的详细介绍。
|
13天前
|
运维 Serverless API
Serverless 应用引擎产品使用之在阿里函数计算中开启函数计算 API 接口如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
111 6