音视频开发基础概述 - PCM、YUV、H264、常用软件介绍

前言相对而言,音视频开发应该算门槛稍高的,记得我刚开始学习的时候,看别人的博客几乎都看不懂。后来总结了一下,看不懂的原因是没有掌握相关基础知识,比如一些概念(码率、采样率、帧率等),音视频中的颜色格式(RGB、YUV)、音频格式(PCM)等,写代码之前不了解这些,会写得非常痛苦——因为你不知道为什么要这么写,也不知道写出来的效果是怎样的。基础概念声波声波有三要素:1) 频率,代...
摘要由CSDN通过智能技术生成

前言

相对而言,音视频开发算是有些门槛的。记得我第一次接触的时候,看别人的博客都看不懂。特别是写代码的时候,非常痛苦,只能抄别人的代码,却不知道为什么要这么写,也不知道应该怎么调整。后来总结了一下,痛苦的原因是在写代码之前没有掌握相关的基础知识,因此现在特地写了这样一篇博客,希望对大家有所帮助。

另,这篇博客主要是对自己做的笔记的一些总结,前前后后参考了许多文章和一些书籍,内容之间的连贯性或许不够好,也可能存在一些错误,如果发现了还望评论指正。

基本概念

声波

声波有三要素:

1) 频率,代表音调,即常说的高音、低音。频率越低,波形越长,穿越障碍物时能量衰减越小,可传播距离越远。

2) 振幅,代表响度,即音量大小。

3) 波形,代表音色,同样的音调和响度,钢琴和小提琴的声音听起来完全不同,这就是音色。波的形状决定了声音的音色,钢琴和小提琴的音色不同就是因为它们的介质所产生的波形不同。

人类耳朵的听力有一个频率范围,大约是 20Hz ~ 20kHz,而根据奈奎斯特定理(采样定理),按比声音最高频率高两倍以上的频率对声音进行采样,能够保证采样声音能够数字化,所以采样频率一般为 44.1kHz。

数字音频

数字音频有三个重要概念:

1) 采样,将信号从连续时间域上的模拟信号转换到离散时间域上的离散信号的过程。

2) 量化,指将信号的连续取值(或者大量可能的离散取值)近似为有限多个(或较少的)离散值的过程:

量化

一般用 8bit 或 16bit 来量化声音的一个采样。按 16bit 量化时有 65536 个可能取值的数字信号,比 8bit(256 个可能值) 更为精细。

3) 编码,即按照一定的格式压缩、存储采样和量化后的数据。裸数据格式通常为 PCM 格式。

PCM

通常音频的裸数据格式为脉冲编码调制(Pulse-code modulation,PCM)数据,描述一段 PCM 数据一般需要一下几个概念:

1) 量化格式(sampleFormat),也被称为位深度,比如 16bit 表示的数据格式,有 65536 个可能值,则位深度为 65536。

2) 采样率(sampleRate),也称为采样速度或者采样频率,定义了每秒从连续信号中提取并组成离散信号的采样个数,用赫兹(Hz)来表示。一般为 44.1kHz,指 1 个声道 1 秒钟有 44.1k 个数据,注意单位为“个”,每个数据的单位可以是 8bit、16bit 或其它,这是采样精度。采样频率的倒数叫作采样周期或采样时间,它是采样之间的时间间隔。

3) 声道数(channel)。分为单声道(mono)和立体声(stereo)两种,单声道只记录一种音源;立体声把现场各个方位的声音单独记录下来,并在播放时模拟当时的场景,可以营造出现场的逼真氛围。其中立体声有双声道、4.1 环绕立体声、5.1 环绕立体声几种。音视频开发中常用的只有单声道、双声道两种。

比特率(码率)

比特率指每秒传送的比特数,单位为 bps(Bit Per Second),比特率越高,传送数据速度越快。声音中的比特率是指将模拟声音信号转换成数字声音信号后,单位时间内的二进制数据量,是间接衡量音频质量的一个指标。 多媒体行业在指音频或者视频在单位时间内的数据传输率时通常使用码流或码率,单位是 kbps(千位每秒)。视频中的码率的概念与声音中的相同,都是指由模拟信号转换为数字信号后,单位时间内的二进制数据量。比如 1.44Mbps,就是 1 秒内到达的数据量为 1.44Mb。(注意,是 bit,不是 byte)。

音频中比特率的计算公式如下:

比特率 = 采样率 * 采样精度 * 声道数目

视频中比特率的计算公式如下:

比特率 = 帧率 * 每帧数据大小

码率是音视频开发中非常重要的一个考虑因素,如果太大,则图像所占的内存也随之上升;太小,视频清晰度又会不足,因此码率的值需要根据实际情况权衡确定。

音频编码

压缩编码的原理实际上是压缩掉冗余信号,冗余信号指不能被人耳感知到的信号,包含人耳听觉范围之外的音频信号以及被掩盖掉的音频信号等。视频中的音频编码码率一般为 128kbit/s。

常见的压缩编码算法有:

1) WAV 编码

WAV 不会压缩音频,其中一种实现是在 PCM 数据前面加上 44 字节,分别描述 PCM 的采样率、声道数、数据格式等信息。

特点:音质非常好,大量软件都支持。
使用场合:多媒体开发的中间文件、保存音乐和音效素材。

PCM 转 WAV 的具体实现可以看我的另一篇博客:使用 AudioRecord 录制 PCM、WAV 文件࿰

  • 23
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
可以使用FFmpeg库来保存视频文件,具体实现可以参考以下代码: ```c++ #include <iostream> #include <string> #include <sstream> #include <fstream> #include <chrono> #include <thread> #include <opencv2/opencv.hpp> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> using namespace std; using namespace cv; int main(int argc, char* argv[]) { // 初始化FFmpeg库 av_register_all(); avcodec_register_all(); // 打开视频文件 string filename = "test.mp4"; AVFormatContext* format_ctx = nullptr; if (avformat_open_input(&format_ctx, filename.c_str(), nullptr, nullptr) != 0) { cerr << "Failed to open video file " << filename << endl; return -1; } // 查找视频流 int video_stream_index = -1; for (unsigned int i = 0; i < format_ctx->nb_streams; i++) { if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; break; } } if (video_stream_index == -1) { cerr << "Failed to find video stream in " << filename << endl; avformat_close_input(&format_ctx); return -1; } // 获取视频流的解码器 AVCodec* codec = avcodec_find_decoder(format_ctx->streams[video_stream_index]->codecpar->codec_id); if (codec == nullptr) { cerr << "Failed to find codec for video stream in " << filename << endl; avformat_close_input(&format_ctx); return -1; } // 打开解码器 AVCodecContext* codec_ctx = avcodec_alloc_context3(codec); if (avcodec_parameters_to_context(codec_ctx, format_ctx->streams[video_stream_index]->codecpar) != 0) { cerr << "Failed to copy codec parameters to decoder context in " << filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); return -1; } if (avcodec_open2(codec_ctx, codec, nullptr) != 0) { cerr << "Failed to open codec for video stream in " << filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); return -1; } // 创建视频文件 string output_filename = "output.mp4"; AVFormatContext* output_format_ctx = nullptr; if (avformat_alloc_output_context2(&output_format_ctx, nullptr, nullptr, output_filename.c_str()) != 0) { cerr << "Failed to create output format context for " << output_filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); return -1; } // 添加视频流 AVStream* output_stream = avformat_new_stream(output_format_ctx, nullptr); if (output_stream == nullptr) { cerr << "Failed to create output video stream for " << output_filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); avformat_free_context(output_format_ctx); return -1; } output_stream->id = output_format_ctx->nb_streams - 1; if (avcodec_parameters_copy(output_stream->codecpar, format_ctx->streams[video_stream_index]->codecpar) != 0) { cerr << "Failed to copy codec parameters to output video stream in " << output_filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); avformat_free_context(output_format_ctx); return -1; } output_stream->codecpar->codec_tag = 0; if (avformat_write_header(output_format_ctx, nullptr) != 0) { cerr << "Failed to write output file header for " << output_filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); avformat_free_context(output_format_ctx); return -1; } // 分配解码缓存 AVFrame* frame = av_frame_alloc(); AVPacket packet; av_init_packet(&packet); // 读取视频帧并保存 while (av_read_frame(format_ctx, &packet) == 0) { if (packet.stream_index == video_stream_index) { if (avcodec_send_packet(codec_ctx, &packet) != 0) { cerr << "Failed to send packet to decoder in " << filename << endl; break; } while (avcodec_receive_frame(codec_ctx, frame) == 0) { // 将YUV图像转换为BGR图像 Mat bgr_image(frame->height, frame->width, CV_8UC3); SwsContext* sws_ctx = sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format, frame->width, frame->height, AV_PIX_FMT_BGR24, SWS_BILINEAR, nullptr, nullptr, nullptr); uint8_t* data[AV_NUM_DATA_POINTERS] = { 0 }; data[0] = bgr_image.data; int linesize[AV_NUM_DATA_POINTERS] = { 0 }; linesize[0] = bgr_image.step; sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, data, linesize); sws_freeContext(sws_ctx); // 保存BGR图像 AVPacket output_packet; av_init_packet(&output_packet); output_packet.data = nullptr; output_packet.size = 0; avcodec_send_frame(codec_ctx, frame); while (avcodec_receive_packet(codec_ctx, &output_packet) == 0) { output_packet.stream_index = output_stream->id; av_write_frame(output_format_ctx, &output_packet); av_packet_unref(&output_packet); } // 显示BGR图像 imshow("BGR Image", bgr_image); waitKey(1); } } av_packet_unref(&packet); } // 释放资源 av_write_trailer(output_format_ctx); avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); avformat_free_context(output_format_ctx); av_frame_free(&frame); return 0; } ``` 这段代码可以读取一个视频文件,将其中的每一帧转换为BGR格式的图像,并保存为另一个视频文件。其中,使用了OpenCV库来显示BGR图像,使用了FFmpeg库来读取和保存视频文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值