windows平台ffmpeg学习笔记(二)

简介: 笔记

六、音频转码


本节将PCM转为AAC格式。再来复习一下FFmpeg的工作流程:

60.png


本次demo也是基于保存网络流到本地的基础上的,可以看出,ffmpeg的使用流程是相当固定的。

做出如下修改:

  1. 输入为麦克风,获取PCM数据,输入路径从设备管理器可以查看:61.png

#include <dshow>
//字符转码
static char *dup_wchar_to_utf8(const wchar_t *w)
{
    char *s = NULL;
    int l = WideCharToMultiByte(CP_UTF8, 0, w, -1, 0, 0, 0, 0);
    s = (char *)av_malloc(l);
    if (s)
        WideCharToMultiByte(CP_UTF8, 0, w, -1, s, l, 0, 0);
    return s;
}
//设置输入路径
//避免找不到中文符号
string fileAudioInput = dup_wchar_to_utf8(L"麦克风阵列=Realtek High Definition Audio");
int ret = OpenInput(fileAudioInput);

2. 初始化的方法中添加设备注册

//ffmpeg初始化
void  Init() {
    av_register_all();
    avcodec_register_all();
    avfilter_register_all();
    avformat_network_init();
    avdevice_register_all();//设备注册
    av_log_set_level(AV_LOG_ERROR);
}

3. 输出文件修改

OpenOutput("C:/Users/bian/Desktop/aac.ts");

4. 初始化CodecFilter,Codec编码器等,代码太多,不过多描述

下面附上github源码地址

做了几个简单的demo程序了,感觉好多方法不知道参数的意义,没有入门理解ffmpeg,后面要了解一引起基本原理了。


七、Ffmpeg重要数据结构


本章简单探索一下ffmpeg的一些数据结构,这样在后面的学习过程中可以有自己的思考,更能深入得理解为什么要这样写,有新的需求该如何写。

重点学习以下几个结构体:

  1. AVFormatContext
  2. AVFrame
  3. AVPacket


八、搭建简单直播系统


直播架构图:

62.png

其实在讲转码的时候已经学习过了。下面学习一下使用wireshark抓取rtp包来分析。

首先安装WireShark和npcap。

打开WireShark,选择本地连接即可。

设置使用UDP还是TCP传输:

AVDictionary *options = nullptr;
//参数设置使用UDP传输
av_dict_set(&options, "rstp_transport", "udp", 0);
int ret = avformat_open_input(&inputContext, inputUrl.c_str(), nullptr, &options);


八、音频裁剪

使用音频裁剪的流程与前面的工程类似,初始化,打开输入流,打开输出流,不同的地方在于把指定要裁剪的数据写入到输出文件。

理解本项目的关键在于理解AVPacketAVRational的数据结构,尤其是pts和dts的理解,下面简单讲一下这两个数据结构的关键内容。

AVPacket的dts和pts

FFmpeg里有两种时间戳:DTS(Decoding Time Stamp)和PTS(Presentation Time Stamp)。 顾名思义,前者是解码的时间,后者是显示的时间。要仔细理解这两个概念,需要先了解FFmpeg中的packet和frame的概念。

FFmpeg中用AVPacket结构体来描述解码前或编码后的压缩包,用AVFrame结构体来描述解码后或编码前的信号帧。 对于视频来说,AVFrame就是视频的一帧图像。这帧图像什么时候显示给用户,就取决于它的PTS。DTS是AVPacket里的一个成员,表示这个压缩包应该什么时候被解码。 如果视频里各帧的编码是按输入顺序(也就是显示顺序)依次进行的,那么解码和显示时间应该是一致的。可事实上,在大多数编解码标准(如H.264或HEVC)中,编码顺序和输入顺序并不一致,于是才会需要PTS和DTS这两种不同的时间戳。

每个AVPacket的时间戳间隔是固定的,那么这个间隔如何计算呢?答案是denominator/帧率,那时间戳间隔如何用秒或者微秒表示呢?

在FFMPEG中有三种时间单位:秒、微秒和dts/pts。从dts/pts转化为微秒公式:

dts* AV_TIME_BASE/ denominator

其中AV_TIME_BASE为1,000,000,denominator为90,000。

现在就更好的理解了,denominator其实就是把一秒等分的个数。例如帧率为30,也就是一秒30帧,denominator为90000,一帧其实是90000/30=3000个,即(1秒/90000)*3000。

denominator是AVRational结构体变量:

typedef struct AVRational{
    int num; ///< Numerator
    int den; ///< Denominator
} AVRational;

同时根据上面所述,dts是某一帧的解码时间,pts是显示时间,那么pts肯定要大于dts的,因为要先解码才能播放吧,这一点一定要弄清楚。

好下,下面上主程序:

int main()
{
    //定义裁剪位置,1)按包个数来裁剪,对应时间与帧率有关;2)根据时间戳裁剪
    int startPacketNum = 200;   //开始裁剪位置
    int discardPacketNum = 200; //裁剪的包个数
    int count = 0;
    //记录startPacketNum最后一个包的pts和dts,本例中的视频源pts和dts是不同的
    int64_t lastPacketPts = AV_NOPTS_VALUE;
    int64_t lastPacketDts = AV_NOPTS_VALUE;
    //时间戳间隔,不同的视频源可能不同,我的是3000
    int timerIntervel = 3000;
    int64_t lastPts = AV_NOPTS_VALUE;
    Init();
    //只支持视频流,不能带有音频流
    int ret = OpenInput("gaoxiao-v.mp4");//视频流,注意输入如果带有音频会失败,导致音频与视频不同步
    if (ret >= 0) {
        ret = OpenOutput("gaoxiao-v-caijian.mp4");
    }
    if (ret < 0) goto Error;
    while (true) {
        count++;
        auto packet = ReadPacketFromSource();
        if (packet) {
            if (count <= startPacketNum || count > startPacketNum + discardPacketNum) {
                if (count >= startPacketNum + discardPacketNum) {
                    //需要调整dts和pts,调整策略和视频源的pts和dts的规律有关,不是固定的
                    packet->dts = -6000 + (count - 1 - discardPacketNum) * timerIntervel;
                    if (count % 4 == 0) {
                        packet->pts = packet->dts;
                    }
                    else if (count % 4 == 1) {
                        packet->pts = packet->dts + 3000;
                    }
                    else if (count % 4 == 2) {
                        packet->pts = packet->dts + 15000;
                    }
                    else if (count % 4 == 3) {
                        packet->pts = packet->dts + 6000;
                    }
                }
                ret = WritePacket(packet);
            }
        }
        else {
            break;
        }
    }
    cout << "cut file end\n" << endl;
Error:
    CloseInput();
    CloseOutput();
...
    return 0;
}

关于上面代码中不理解的地方可能就是调整dts和pts的策略了,注释中也说明了,这跟视频的dts和pts的策略有关,我为什么这样写呢?下面我打印了原始视频的dts和pts

pakcet.pts=0,pakcet.dts=-6000  //第一个packet,count=1
pakcet.pts=12000,pakcet.dts=-3000   //count=2
pakcet.pts=6000,pakcet.dts=0  //count=3
pakcet.pts=3000,pakcet.dts=3000  //count=4
pakcet.pts=9000,pakcet.dts=6000  //count=5
pakcet.pts=24000,pakcet.dts=9000  //count=6
pakcet.pts=18000,pakcet.dts=12000  //count=7
pakcet.pts=15000,pakcet.dts=15000  //count=8
pakcet.pts=21000,pakcet.dts=18000
pakcet.pts=36000,pakcet.dts=21000
pakcet.pts=30000,pakcet.dts=24000
pakcet.pts=27000,pakcet.dts=27000
pakcet.pts=33000,pakcet.dts=30000
pakcet.pts=48000,pakcet.dts=33000
pakcet.pts=42000,pakcet.dts=36000
pakcet.pts=39000,pakcet.dts=39000
pakcet.pts=45000,pakcet.dts=42000
pakcet.pts=60000,pakcet.dts=45000
pakcet.pts=54000,pakcet.dts=48000
pakcet.pts=51000,pakcet.dts=51000
pakcet.pts=57000,pakcet.dts=54000
pakcet.pts=72000,pakcet.dts=57000
pakcet.pts=66000,pakcet.dts=60000
pakcet.pts=63000,pakcet.dts=63000
...

从上面的Log中可以看到dts是非常有规律的,起始为-6000,按等差3000递增。而pts看着似乎有点乱,但是有有一定的规律,就是在特定packet包ptd=dts,这个packet的个数为4的整倍数,而不是4的整倍数的也很容易找出规律,模4余1时,pts=dts+3000,余2时pts=dts+15000,余3时pts=dts+6000。这样就OK了。

目录
相关文章
|
3天前
|
Docker Windows Python
Windows-Docker学习笔记(一)
本文介绍了Windows环境下Docker的安装与使用方法。首先通过链接下载Windows版本的Docker,然后按步骤配置:启用Windows功能、设置默认WSL版本为2,并检查Docker及其组件是否正常工作。接着详细列出了Docker的基础命令及其用法,包括搜索、下载镜像、管理容器等。同时介绍了Dockerfile的编写方法及其构建过程,以及docker-compose.yml文件的用途。最后提供了运行和管理容器的具体示例。
Windows-Docker学习笔记(一)
WK
|
26天前
|
存储 JavaScript 前端开发
如何在Windows平台上手micro:bit
micro:bit是一款口袋大小的可编程计算机,使用ARM处理器,跨平台兼容性强,适用于青少年学习编程。通过USB接口轻松连接Windows电脑,找到“MICROBIT”文件夹开始互动。提供MakeCode(支持拖拽编程及JavaScript)和MicroPython平台,满足不同编程需求。创建项目后,下载.hex文件至micro:bit,即可运行程序,展现创意成果。
WK
23 1
|
29天前
|
Windows
Windows平台如何修改监听的服务名称?
【8月更文挑战第15天】在Windows平台上可透过注册表编辑器、命令提示符或第三方工具修改服务的显示名称。首先,通过注册表编辑器找到`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services`下的目标服务,修改其“DisplayName”键值。或者,在命令提示符中使用`sc config`命令来变更服务名称。此外,利用第三方工具如Windows Service Manager也能简化此过程。修改前请确保了解可能的影响并做好备份。
|
15天前
|
编解码 开发工具 数据安全/隐私保护
如何快速实现Windows平台屏幕摄像头采集并推送RTMP|轻量级RTSP服务能力?
一个好的推送模块,除了实现高效率的编码传输外,还要有好的音视频采集机制和灵活的架构支持,便于后期功能扩展,比如实时快照、预览、实时录像等。除此之外,还要有好的交互机制(比如envent callback)、低延迟和长期运行稳定的性能。
|
1月前
|
XML C# 数据格式
绝密档案曝光!Windows平台如何深挖一个dll背后的神秘依赖,揭露隐藏的秘密!
【8月更文挑战第14天】在Windows系统中,动态链接库(DLL)对程序运行至关重要。了解DLL的依赖关系有助于软件的调试与优化。本文以具体案例演示如何查看DLL依赖。首先确保环境已安装Windows及具备基本开发知识。
41 0
|
1月前
|
编解码 开发工具 C#
[大牛直播SDK]Windows平台RTMP直播推送模块功能设计
大牛直播SDK采用全自研框架,具备高度可扩展性与自适应算法,显著降低延迟并提高采集编码效率。SDK以模块化设计,支持RTMP推流及多种音视频编码格式(如AAC、SPEEX、H.264、H.265),并能与播放器SDK组合实现丰富功能,包括流媒体转发、内置RTSP服务等。提供了详尽的参数配置选项,支持多摄像头、屏幕采集与水印叠加,并兼容Windows 7及以上操作系统。该SDK以C++/C#双接口形式提供,集成简便,同时包含调试与发布版本库,便于开发者快速上手。此外,支持断网重连、实时预览及多种编码前后的数据对接需求。
|
C语言 Windows C++
在windows XP系统下编译和使用ffmpeg
最近在做流媒体开发这一块,在服务器端,所用的live555不支持mp4,avi等视频容器格式,所以打算运用ffmpeg来进行扩展。将MP4文件先运用ffmpeg进行解析,解析成live555所支持的基本视频流格式(如H.264等)和音频流格式,再运用live555中对基本音视频流的处理方式处理后,发送到客户端进行解码播放,这其中运用到了ffmpeg库,为了便于调试,开发环境初步选择了在wi
2153 0
|
10天前
|
网络安全 虚拟化 Windows
windows 11安装openSSH server 遇到的"kex_exchange_identification: read: Connection reset"问题
windows 11安装openSSH server 遇到的"kex_exchange_identification: read: Connection reset"问题
|
20天前
|
PHP Windows
【Azure App Service for Windows】 PHP应用出现500 : The page cannot be displayed because an internal server error has occurred. 错误
【Azure App Service for Windows】 PHP应用出现500 : The page cannot be displayed because an internal server error has occurred. 错误
|
30天前
|
开发框架 .NET API
Windows Server 2022 安装IIS 报错 访问临时文件夹 C:\WINDOWS\TEMP\3C 读取/写入权限 错误: 0x80070005
Windows Server 2022 安装IIS 报错 访问临时文件夹 C:\WINDOWS\TEMP\3C 读取/写入权限 错误: 0x80070005
64 0