【音视频 | opus】opus编码的Ogg封装文件详解

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 opus 编码的 Ogg 封装文件🍭
😎金句分享😎:🍭🍭

本文未经允许,不得转发!!!

opus和Ogg相关系列文章:
1、RFC3533 :Ogg封装格式版本 0(The Ogg Encapsulation Format Version 0)
2、Ogg封装格式详解——包含Ogg封装过程、数据包(packet)、页(page)、段(segment)等
3、libogg库详解介绍以及使用——附带libogg库解析.opus文件的C源码
4、RFC7845:Opus音频编解码器的Ogg封装(Ogg Encapsulation for the Opus Audio Codec)
5、opus编解码库(opus-1.4)详细介绍以及使用——附带解码示例代码
6、opus编码的Ogg封装文件详解

目录

  • 🎄一、概述
  • 🎄二、ID头部数据(ID Header)
    • ✨2.1、ID 头部数据的介绍
    • ✨2.2、.opus 文件 ID头部数据 分析
  • 🎄三、注释头部数据(Comment Header)
    • ✨3.1、注释头部数据的介绍
    • ✨3.2、.opus 文件 注释头部数据 分析
  • 🎄四、opus 编码数据包
  • 🎄五、opus编码的Ogg封装文件解码C语言源码
  • 🎄六、总结


在这里插入图片描述

🎄一、概述

前面的文章 Ogg封装格式详解 介绍了Ogg格式的封装,介绍了封装过程、Ogg的页结构、Ogg的段,但没涉及到每个段的内容,因为这跟编码有关系。本文就介绍 opus 编码在 Ogg 封装文件中是怎样存储的,同样地,从一个 opus 编码的Ogg文件去查看十六进制,最终了解 opus 编码 Ogg 封装文件的整个结构,这样就可以轻易将这类文件解码为PCM了。

opus 编码的Ogg文件结构如下图:

  • 逻辑Ogg比特流中的第一个数据包必须包含ID头部数据(ID Header),该头部数据将流唯一标识为Opus音频。
  • 逻辑Ogg比特流中的第二个数据包必须包含注释头部数据(Comment Header),其中包含用户提供的元数据。
  • 之后的所有页面(page)都是音频数据页面。
    在这里插入图片描述
    下文分析的Ogg封装文件为 48000Hz-s16le-1ch-ChengDu.opus,下载链接:https://download.csdn.net/download/wkd_007/88492683

在这里插入图片描述

🎄二、ID头部数据(ID Header)

✨2.1、ID 头部数据的介绍

opus编码的逻辑Ogg比特流中,第一页必须包含ID头部数据,其结构如下图,包括8个字段,各个字段代表的含义如下:

在这里插入图片描述

  • 1、Magic Signature(8字节):

    这是一个8位字节(64位)字段,允许编解码器识别,并且是人类可读的。它按顺序包含以下的字母:

    0x4F 'O'
    0x70 'p'
    0x75 'u'
    0x73 's'
    0x48 'H'
    0x65 'e'
    0x61 'a'
    0x64 'd'
    

    从“Op”开始有助于将其与音频数据包区分开来,因为这是一个无效的TOC序列。

  • 2、Version (1字节, unsigned):
    对于此版本的封装规范,版本号必须始终为“1”。实现应该将版本号的前四位与已识别规范的版本号匹配的流视为与该规范向后兼容。也就是说,版本号可以分为“主要”和“次要”版本子字段,次要子字段的变化(在较低的四位中)表示兼容的变化。例如,本规范的实现应该接受版本号为“15”或更低的任何流,并且应该假设版本号为”16“或更高的任何流不兼容。选择初始版本“1”是为了防止实现依赖此八位字节作为“OpusHead”字符串的null终止符。

  • 3、Output Channel Count ‘C’ (1字节, unsigned)
    这是输出通道的数量。这可能与编码信道的数量不同,编码信道的数目可以在逐包的基础上改变。此值不得为零。最大允许值取决于通道映射族,可能大到255。详见[RFC7845]文档的第5.1.1节。

  • 4、Pre-skip (2字节, unsigned, little endian):

    这是开始播放时要从解码器输出中丢弃的采样数(48 kHz),也是要从页面的颗粒位置中减去以计算其PCM采样位置的数量。当裁剪现有Ogg Opus流的开头时,建议预跳过至少3840个样本(80ms),以确保解码器完全收敛。

  • 5、Input Sample Rate (4字节, unsigned, little endian):

    这是原始输入(编码前)的采样率,单位为Hz。该字段不是用于播放编码数据的采样率。

    Opus可以在4、6、8、12和20kHz的内部音频带宽之间切换。流中的每个数据包可以具有不同的音频带宽。不管音频带宽如何,参考解码器都支持以8、12、16、24或48kHz的采样率对任何流进行解码。传递到编码器的音频的原始采样率不被有损压缩所保留。

    Ogg Opus播放器应根据以下程序选择播放采样率:

    1.如果硬件支持48 kHz播放,则以48 kHz进行解码。

    2.否则,如果硬件的最高可用采样率是支持的速率,则以该采样率进行解码。

    3.否则,如果硬件的最高可用采样率小于48kHz,则以高于最高可用硬件速率的下一个更高的Opus支持速率进行解码并重新采样。

    4.否则,以48 kHz解码并重新采样。

    然而,“输入采样率”字段允许复用器将原始输入流的采样率作为元数据传递。当用户需要输出采样率来匹配输入采样率时,这是有用的。例如,当不播放输出时,将PCM格式样本写入磁盘的实现可能会选择将音频重新采样回原始输入采样率,以减少用户的意外,因为用户可能会合理地期望以相同的采样率返回文件。

    零值表示“未指定”。复用器应该写入实际输入采样率或零,但使用该字段执行某些操作的实现应该注意,如果给定疯狂的值(例如,如果请求,不要实际将输出上采样到10MHz),则行为要理智。实现应支持 8kHz 和 192kHz(包括8kHz)之间的输入采样率。此范围之外的速率可以通过回落到默认速率 48kHz 来忽略。

  • 6、Output Gain (2字节, signed, little endian):

    这是解码时要应用的增益。它是对解码器输出进行缩放以获得所需播放量的因子的20*log10,存储在具有8个小数比特的16比特、带符号的2的补码定点值中(即Q7.8[Q-NOTATION])。

    为了应用增益,实现可以使用以下内容:

    sample *= pow(10, output_gain/(20.0*256))
    

    其中“output_gain”是来自标头的原始16位值。

    玩家和媒体框架应该默认应用它。如果播放器选择应用任何音量调整或增益修改,如R128_TRACK_AIN(见第5.2节),则除了此输出增益外,还必须应用该调整,以实现标准化音量的播放。

    复用器应该将该字段设置为零,而不是在编码之前应用任何增益,只要这是可能的,并且不会与用户的愿望相冲突。非零输出增益表示在编码之后调整了增益,或者用户希望在保持恢复原始信号幅度的能力的同时调整增益以进行回放。

    尽管输出增益具有巨大的范围(+/-128 dB,足以将听不见的声音放大到身体疼痛的阈值),但大多数应用程序只能合理地使用零附近这个范围的一小部分。大范围的部分作用是确保增益始终可以在OpusHead和R128增益标签之间无损传输(见下文)而不会饱和。

  • 7、Channel Mapping Family (1字节, unsigned):

    这个八位字节表示输出通道的顺序和语义。

    该八位位组的每个当前指定值都指示一个映射族,该映射族定义了一组允许的通道计数,以及每个允许的通道数的有序通道名称集。详见[RFC7845]文档的第5.1.1节。

  • 8、Channel Mapping Table:

    此表定义了从编码流到输出通道的映射。其内容见[RFC7845]文档的第5.1.1节。ID标头中的所有字段都是必需的,但“通道映射表”除外,当通道映射族为0时,必须省略该字段,否则为必需字段。如果流包含的ID标头没有足够的数据用于这些字段,即使它包含有效的“魔术签名”,实现也应该将其视为无效。该规范的未来版本,甚至是向后兼容的版本,可能会在ID头中包含额外的字段。如果ID标头具有兼容的主版本,但具有较大的次版本,则实现不得将其视为无效,因为它包含此处未指定的其他数据,前提是它仍然在第一页上完成。

✨2.2、.opus 文件 ID头部数据 分析

现在按照上面的字段,分析opus编码的Ogg文件,用Notepad打开 48000Hz-s16le-1ch-ChengDu.opus 并查看十六进制模式:

在这里插入图片描述

  • 前面蓝色背景的部分(包括0x13)是Ogg页头部数据,不清楚的看 上篇文章 的 4.2 节;
  • 紫色框开始是opus编码的ID头部数据,内容是8个字符:OpusHead
  • 接下去的蓝色框是版本号,目前的版本,该值为 1
  • 紧接着的蓝色框是声道数量,这里值为 1 ,表示单声道;
  • 后面的橙色框表示 “开始播放时要从解码器输出中丢弃的采样数”,值为0x0138
  • 接着的绿色框表示 “原始输入(编码前)的采样率”,值为0xbb80,对应十进制 48000
  • 后面的蓝色框表示 “解码时要应用的增益”;
  • 后面的黑色框表示 “表示输出通道的顺序和语义”,值为 0 ,代表使用映射族0,允许的通道数:1或2,且省略通道映射表(Channel Mapping Table);

在这里插入图片描述

🎄三、注释头部数据(Comment Header)

✨3.1、注释头部数据的介绍

opus编码的逻辑Ogg比特流中,第二页必须包含注释头部数据。它可能跨越多个页面(page),从逻辑流的第二页开始。无论它跨越多少页,注释头数据包都必须结束在一个完整的页面,其结构如下图,包括6个字段,各个字段代表的含义如下:
在这里插入图片描述

  • 1、Magic Signature(8个字节):

    这是一个8位字节(64位)字段,允许编解码器识别,并且是人类可读的。它按顺序包含以下神奇数字:

    0x4F 'O'
    0x70 'p'
    0x75 'u'
    0x73 's'
    0x54 'T'
    0x61 'a'
    0x67 'g'
    0x73 's'
    

    从“Op”开始有助于将其与音频数据包区分开来,因为这是一个无效的TOC序列。

  • 2、Vendor String Length (4个字节, unsigned, little endian):

    此字段给出下个字段Vendor String的长度,单位为八位字节。它不得指示供应商字符串比数据包的其余部分长。

  • 3、Vendor String (variable length, UTF-8 vector):

    这是一个用于供应商信息的简单可读标签,编码为UTF-8字符串[RFC3629]。不需要终止空八位字节。

    此标签旨在识别编解码器编码器和封装实现,以跟踪技术行为的差异。面向用户的应用程序可以使用“ENCODER”用户注释标记来标识自己。

  • 4、User Comment List Length (4个字节, unsigned, little endian):

    此字段指示用户提供的注释个数。它可能表示用户提供的注释为零,在这种情况下,数据包中没有其他字段。它不能表明有太多的注释,以至于注释字符串长度需要比数据包其余部分更多的数据。

  • 5、User Comment #i String Length (4个字节, unsigned, little endian):

    此字段给出下个字段User Comment #i String的长度,单位为八位字节。“用户评论列表长度”字段指示的每个用户评论都有一个。它不能指示字符串比数据包的其余部分长。

  • 6、User Comment #i String (variable length, UTF-8 vector):

    此字段包含编码为UTF-8字符串的单个用户注释[RFC3629]。“用户评论列表长度”字段指示的每个用户评论都有一个。

✨3.2、.opus 文件 注释头部数据 分析

下图是用Notepad打开 48000Hz-s16le-1ch-ChengDu.opus 并查看十六进制模式的截图,这里分析的是第二页数据:
在这里插入图片描述

  • 前面蓝色背景的部分是Ogg页头部数据,不清楚的看 上篇文章 的 4.2 节第二页数据;
  • 紫色框开始是opus编码的注释头部数据,内容是8个字符:OpusTags
  • 接下去的红色色框的4个字节是供应商字符串长度, 此字段给出下个字段Vendor String的长度,这里是0x0b
  • 紧接着的红色框的11个字节就是供应商字符串,内容是libopus 1.1
  • 后面的绿色框表示 “注释个数”,值为0x01,表示只有一个注释 ;
  • 接着的蓝色框表示 “第一个注释字符串的长度”,值为0x25,对应十进制 37,表示后面有37个字符的注释;
  • 后面的橙色框表示 “注释字符串”,内容是ENCODER=opusenc form opus-tools 0.1.8,共37个字符;

在这里插入图片描述

🎄四、opus 编码数据包

在一个opus编码的Ogg格式文件中,除了一个ID头部数据包和一个 注释头部数据包(有时可以跨多页),剩下的都是 opus编码音频数据包,音频数据包也是存储在Ogg格式文件的各个段(Segment)中,下面从一个实际的Ogg文件分析音频数据包。

下图是用Notepad打开 48000Hz-s16le-1ch-ChengDu.opus 并查看十六进制模式的截图,这里分析的是第三页数据:

在这里插入图片描述

  • 图中黑色框表示该Ogg页数据中有 0x33(十进制51)个段(Segment),但并不表示有51个opus音频数据包,因为段的长度为0xff时,表示该段存不下一个音频数据包,下个段仍然是这个包的数据。
  • 蓝色背景的51个字节就是各个段的长度,可以看到第一个段长度是0xff,表示第一个音频包存储了2个段,第一个音频包的实际长度应该是0xff+0x06;后面49个段长度中,都没有0xff,说明每个都存储了一个音频数据包。所以这一页应该是50个音频数据包。每个数据包读取后,都可以直接送到 opus-1.4 的库去解码成PCM音频数据。

在这里插入图片描述

🎄五、opus编码的Ogg封装文件解码C语言源码

清楚了前面的知识,就可以对opus编码的Ogg文件进行解码了,你可以使用 libogg库加上 opus-1.4 的库进行解码,那样会更简洁一点,下面给出的源码只使用了opus-1.4去解码音频包,有很大的参考价值:

opusDec.h

/*** @file opusDec.h* @author wkd_007* @date 2023-10-27 15:47:38*/
#ifndef	__OPUS_DEC_H__
#define __OPUS_DEC_H__#include "opus/opus.h"#define MAX_OPUS_DEC_FRAME  48000   // opus解码最大采样点个数,如果个数时间小于120ms,可能停止解码,这里设置1000ms的个数
#define OPUS_DEC_CHANNELS   2
class COpusDec
{
public:COpusDec();~COpusDec();int CreateOpusDecoder(int sampleRate, int channels);int OpusDecode(unsigned char* in_data, int in_len, short *out_buf);private:OpusDecoder *decoder;   // opus 解码器指针int sample_rate;        // 采样率int channel_num;        // 通道数
};
#endif// __OPUS_DEC_H__

opusDec.cpp

/*** @file 	opusDec.cpp* @author 	wkd_007* @brief 	opus 解码* @date 	2023-10-27 15:38:43*/#include <stdio.h>
#include "opusDec.h"COpusDec::COpusDec()
{decoder = NULL;sample_rate = 0;channel_num = 0;
}COpusDec::~COpusDec()
{sample_rate = 0;channel_num = 0;if(decoder){opus_decoder_destroy(decoder);decoder = NULL;}
}int COpusDec::CreateOpusDecoder(int sampleRate, int channels)
{int err = 0;decoder = opus_decoder_create(sampleRate, channels, &err);if(err != OPUS_OK || decoder == NULL){printf("[%s %d]err=%d decoderIsNULL=%d\n",__FILE__,__LINE__,err, decoder == NULL);return -1;}opus_decoder_ctl(decoder, OPUS_SET_LSB_DEPTH(16));sample_rate = sampleRate;channel_num = channels;return err;
}int COpusDec::OpusDecode(unsigned char* in_data, int in_len, short *out_buf)
{if(decoder == NULL)return -1;int frame_size = opus_decode(decoder, in_data, in_len, out_buf, MAX_OPUS_DEC_FRAME, 0);if (frame_size < 0){printf("[%s %d] frame_size=%d in_len=%d\n",__FILE__,__LINE__, frame_size,in_len);return frame_size;}return frame_size;
}

readOggFile.c

// g++ readOggFile.c opusDec.cpp ../../opus-1.4/result_gcc/lib/libopus.a -I ../../opus-1.4/result_gcc/include/#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include "opusDec.h"COpusDec g_OpusDec;// 8字节数组转成 unsigned long long
unsigned long long ToULL(unsigned char num[8], int len)
{unsigned long long ret = 0;if(len==8){	int i=0;for(i=0; i<len; i++){ret |= ((unsigned long long)num[i] << (i*8));}}return ret;
}// 4字节数组转成 unsigned int
unsigned int ToUInt(unsigned char num[4], int len)
{unsigned int ret = 0;if(len==4){int i=0;for(i=0; i<len; i++){ret |= ((unsigned int)num[i] << (i*8));}}return ret;
}int readOggPage(char *oggFile)
{typedef struct PAGE_HEADER{  char           Oggs[4];        unsigned char  ver;unsigned char  header_type_flag;unsigned char  granule_position[8];unsigned char  stream_serial_num[4];unsigned char  page_sequence_number[4];unsigned char  CRC_checksum[4];unsigned char  seg_num;unsigned char  segment_table[];}PAGE_HEADER;FILE *fp=fopen(oggFile,"rb");FILE *fpout=fopen("opus_out.pcm","wb+");;while(fp!=NULL && !feof(fp)){// 1、读取 page_headerPAGE_HEADER page_header;if(1 != fread(&page_header,sizeof(page_header),1,fp))break;printf("page_num:%03u; ",ToUInt(page_header.page_sequence_number, 4));printf("Oggs:%c %c %c %c; ",page_header.Oggs[0],page_header.Oggs[1],page_header.Oggs[2],page_header.Oggs[3]);printf("type=%d, granule_position:%08llu; ", page_header.header_type_flag,ToULL(page_header.granule_position, 8));//printf("seg_num:%d \n",page_header.seg_num);// 2、读取 Segment_tableunsigned char *pSegment_table = (unsigned char *)malloc(page_header.seg_num);fread(pSegment_table,sizeof(unsigned char),page_header.seg_num,fp);// 3、计算段数据总大小unsigned int TotalSegSize = 0;int i=0;for(i=0; i<page_header.seg_num; i++){TotalSegSize += pSegment_table[i];}printf("TotalSegSize:%d \n",TotalSegSize);// 4、读取段数据unsigned char *pSegment_data = (unsigned char *)malloc(TotalSegSize);fread(pSegment_data,sizeof(unsigned char),TotalSegSize,fp);if(page_header.header_type_flag == 4)printf("Last 4 Byte: %x %x %x %x\n",pSegment_data[TotalSegSize-4],pSegment_data[TotalSegSize-3], pSegment_data[TotalSegSize-2],pSegment_data[TotalSegSize-1]);// 5、解码opusunsigned sample_pos = ToULL(page_header.granule_position, 8);if(sample_pos){short out_buf[48000*2] = {0};int decodedLen = 0; // 已经解码的长度int preSegLen =0;	// 前面分段的长度for(i=0; i<page_header.seg_num; i++){if(pSegment_table[i] == 255)// 这个包延续到后面,把该段长度累加起来{preSegLen += pSegment_table[i];continue;}int decFrames = g_OpusDec.OpusDecode((unsigned char*)pSegment_data+decodedLen, (int)preSegLen+pSegment_table[i], out_buf);if(decFrames < 0){printf("decodedLen=%d TotalSegSize=%d pSegment_table[i]=%d %d %d\n",decodedLen, TotalSegSize,pSegment_table[i-1],pSegment_table[i],pSegment_table[i+1]);}else{fwrite(out_buf,decFrames * sizeof(unsigned short),1,fpout);}decodedLen += (preSegLen+pSegment_table[i]);preSegLen = 0;}}free(pSegment_data);free(pSegment_table);}fclose(fp);fclose(fpout);return 0;
}int main()
{g_OpusDec.CreateOpusDecoder(48000, 1);readOggPage((char *)"48000Hz-s16le-1ch-ChengDu.opus");return 0;
}

在这里插入图片描述

🎄六、总结

本文介绍了opus编码的Ogg文件的 ID数据头部、注释数据头部、opus编码数据包的结构和解析,并用一个.opus文件的十六进行带着读者理解各个数据包,最后给出了笔者的C语言代码,将opus编码Ogg格式解析为PCM。看完一定大有所获,别忘记点赞、收藏、支持一下。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/162785.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Gldie复用只取之前decode过的缓存resource,Kotlin

Android Gldie复用只取之前decode过的缓存resource&#xff0c;Kotlin import android.graphics.Bitmap import android.os.Bundle import android.util.Log import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.life…

ARMday1

1、计算机的组成 输入设备-输出设备-运算器-控制器-存储器 输入设备&#xff1a;键盘、鼠标、手柄、扫描仪 输出设备&#xff1a;显示屏、打印机、音响 存储器&#xff1a;存放数据以及指令、是实现“程序存储控制”的基础、外存、内存、cache、寄存器 控制器&#xff08;…

梳理自动驾驶中的各类坐标系

目录 自动驾驶中的坐标系定义 关于坐标系的定义 几大常用坐标系 世界坐标系 自车坐标系 传感器坐标系 激光雷达坐标系 相机坐标系 如何理解坐标转换 机器人基础中的坐标转换概念 左乘右乘的概念 对左乘右乘的理解 再谈自动驾驶中的坐标转换 本节参考文献 自动驾驶…

Android Datastore 动态创建与源码解析

涉及到的知识点 1、协程原理---->很好的博客介绍&#xff0c;一个小故事讲明白进程、线程、Kotlin 协程到底啥关系&#xff1f; 2、Channel知识点---->Android—kotlin-Channel超详细讲解 3、Coroutines : CompletableDeferred and structured concurrency 封装的DataS…

基于Chirp窄带扩频技术的无线混合组网应用,以多角色智能计量插座作为Chirp广域基站,构建边缘计算混合无线网络

随着物联网&#xff08;IoT&#xff09;的不断发展&#xff0c;无线通信技术的需求也在不断增加。Chirp窄带扩频技术是一种具有广泛应用潜力的无线通信技术&#xff0c;它在低功耗、广域覆盖、抗干扰等方面具备独特的优势。本文介绍了如何利用磐启微Chirp技术构建ECWAN无线混合…

卡尔曼滤波之二:Python实现

卡尔曼滤波之二&#xff1a;Python实现 1.背景描述2.构建卡尔曼滤波公式2.1 预测2.2 更新 3.代码实现3.1 输入值3.2 pykalman包实现3.3 不使用Python包实现3.4 效果可视化 参考文献 了解了卡尔曼滤波之一&#xff1a;基本概念&#xff0c;可以结合代码来理解下卡尔曼滤波的2个预…

【数据库】数据库模式 Schema

数据库模式 Schema 1.MySQL2.PostgreSQL3.SQL Server4.Oracle5.SQLite 在数据库的术语中&#xff0c;模式&#xff08;schema&#xff09;是一个逻辑概念&#xff0c;用于组织数据库中的对象。模式中的对象通常包括 表、索引、数据类型、序列、视图、存储过程、主键、外键 等等…

Electron[3] 基础配置准备和Electron入门案例

1 背景 上一篇文章已经分享了&#xff0c;如何准备Electron的基础环境了。但是博客刚发才一天&#xff0c;就发现有人问问题了。经过实践发现&#xff0c;严格按照作者的博客教程走是不会有问题的&#xff0c;其中包括安装的环境版本等都要一致。因为昨天发的博客&#xff0c;…

【论文阅读】Equivariant Contrastive Learning for Sequential Recommendation

【论文阅读】Equivariant Contrastive Learning for Sequential Recommendation 文章目录 【论文阅读】Equivariant Contrastive Learning for Sequential Recommendation1. 来源2. 介绍3. 前置工作3.1 序列推荐的目标3.2 数据增强策略3.3 序列推荐的不变对比学习 4. 方法介绍4…

kafka问题汇总

报错1&#xff1a; 解决方式 1、停止docker服务   输入如下命令停止docker服务 systemctl stop docker 或者service docker stop1   停止成功的话&#xff0c;再输入docker ps 就会提示出下边的话&#xff1a; Cannot connect to the Docker daemon. Is the docker daem…

汽车标定技术(四)--问题分析:多周期测量时上位机显示异常

目录 1.问题现象 2.数据流分析 ​​​​3.代码分析 3.1 AllocDAQ 3.2 AllocOdt 3.3 AllocOdtEntry 4.根因分析及解决方法 4.1 根因分析 4.2 解决方案 1.问题现象 在手撸XCP代码时&#xff0c; DAQ的实现是一大头痛的事情。最初单周期实现还好一点&#xff0c;特别是…

聊一聊关于手机Charge IC的电流流向

关于手机Charge&#xff0c;小白在以前的文章很少讲&#xff0c;一是这部分东西太多&#xff0c;过于复杂。二是总感觉写起来欠缺点什么。但后来想一想&#xff0c;本是抱着互相学习来写文章的心理态度&#xff0c;还是决定尝试写一些。 关于今天要讲的关于手机Charge的内容&a…