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了。

目录
相关文章
|
9月前
|
XML C# 数据格式
掌握了在Windows平台上查看DLL依赖的方法
掌握了在Windows平台上查看DLL依赖的方法
1245 4
|
2月前
|
安全 前端开发 Linux
Immunity CANVAS Professional 7.27 (macOS, Linux, Windows) - 渗透测试和漏洞利用平台
Immunity CANVAS Professional 7.27 (macOS, Linux, Windows) - 渗透测试和漏洞利用平台
100 3
Immunity CANVAS Professional 7.27 (macOS, Linux, Windows) - 渗透测试和漏洞利用平台
|
4月前
|
固态存储 C++ 计算机视觉
Windows平台GIMP 2.10下载教程:零基础入门高级图像编辑
GIMP(GNU Image Manipulation Program)是一款开源跨平台图像编辑工具,支持图层管理、高级修图、色彩校正等功能,广泛应用于平面设计和照片修复。其优势包括全功能免费、插件生态丰富(600+扩展插件)、硬件要求低(1GB内存即可流畅运行)。本文详细介绍GIMP的软件定位、安装流程、首次配置及常见问题解答,帮助用户快速上手并充分利用其强大功能。
|
9月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
294 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
9月前
|
NoSQL Shell MongoDB
Windows 平台安装 MongoDB
10月更文挑战第10天
225 0
Windows 平台安装 MongoDB
|
9月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
900 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
9月前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
231 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
10月前
|
XML Java Android开发
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
GSYVideoPlayer是一款国产移动端视频播放器,支持弹幕、滤镜、广告等功能,采用IJKPlayer、Media3(EXOPlayer)、MediaPlayer及AliPlayer多种内核。截至2024年8月,其GitHub星标数达2万。集成时需使用新版Android Studio,并按特定步骤配置依赖与权限。提供了NormalGSYVideoPlayer、GSYADVideoPlayer及ListGSYVideoPlayer三种控件,支持HLS、RTMP等多种直播链接。
356 18
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
|
9月前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
578 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频