拖了很久的AVI音视频封装实例,花了一天时间终于调完了,兼容性不是太好,但作为参考学习使用应该没有问题。RIFF和AVI以及WAV格式,可以参考前面的一些文章。这里详细介绍将一个H264视频流和一个2通道PCM音频流封装成一个AVI格式的视频文件。测试过可以在暴风,迅雷和VLC播放器上正常播放。

关于RIFF、AVI、WAV格式,可以查看前面的内容:

AVI音视频封装格式学习(一)——微软RIFF文件格式摘要

AVI音视频封装格式学习(二)——AVI RIFF文件参考

AVI音视频封装格式学习(三)——AVI 数据结构解析

AVI音视频封装格式学习(四)——linux系统C语言AVI格式音视频封装应用

如果用第三方工具或是第三方库合成AVI文件,其实是很简单的事,这里不做介绍。如果是需要自己用代码合成AVI文件,我建议一般的步骤是:

(1)将一个视频流封装成AVI文件

(2)将一个视频流和一个音频流合成一个AVI文件

(3)为AVI文件添加索引信息

之所以要分开来做,主要也是方便调试和定位,AVI的中文资料较少,而且它的参数也是比较的复杂。索引信息是为了视频播放时候的快进快退和拖动用的,如果没有这需求也可以不加。

在这里,我使用一个h264文件来模拟视频流,使用一个PCM文件来模拟音频流。视频流和音频流参数可以见下图:

h264媒体信息

PCM音频信息

完整工程文件目录如下:

biao@ubuntu:~/test/AVI_Create$ make clean
rm -f -f *.o test
biao@ubuntu:~/test/AVI_Create$ tree
.
├── avilib.c
├── avilib.h
├── File
│   ├── 2Channel_44100_16bit.pcm
│   ├── out.h264
│   ├── test.avi
│   └── test.h264
├── main.c
├── Makefile
├── MediaStream.c
└── MediaStream.h1 directory, 10 files
biao@ubuntu:~/test/AVI_Create$ 

avilib是用来封装AVI格式,MediaStream用来模拟音视频数据流,main函数里做主要的控制。代码较多,先上main函数代码,完整的工程代码下面有下载链接。

/************************************************************
*FileName: main.h
*Date:     2018-07-15
*Author:   Wen Lee
*Version:  V1.0
*Description:
*Others:
*History:
***********************************************************/#include <stdlib.h>
#include <stdio.h>
#include "avilib.h"
#include "MediaStream.h"#define WRITE_FILE_NAME       "./File/out.h264"
#define WRITE_AVI_NAME      "./File/test.avi"#define AVI_VIDEO_FLAG           "00dc"
#define AVI_AUDIO_FLAG          "01wb"static FILE *g_out_h264_fd = NULL;
static FILE *g_out_avi_fd = NULL;int CreatOuth264File(void)
{g_out_h264_fd = fopen(WRITE_FILE_NAME,"w+");if(NULL==g_out_h264_fd){printf("%s %d fopen error \n",__FILE__,__LINE__);return -1;}return 0;
}int CreatAVIFile(void)
{g_out_avi_fd = fopen(WRITE_AVI_NAME,"w+");if(NULL==g_out_avi_fd){printf("%s %d fopen error \n",__FILE__,__LINE__);return -1;}return 0;
}/*************************************************
Function:    ExportCalcAlign
Description: Data alignment
Input:  u32Data, u8Align
OutPut:
Return: Aligned data
Others:
Author: Caibiao Lee
Date:   2018-03-06
*********************************************************/
unsigned int AVICalcAlign(unsigned int u32Data,unsigned char u8Align)
{return ((u32Data + u8Align - 1) & (~(u8Align - 1)));
}/********************************************************
Function: TestH264ReadAndWrite
Description:将h264文件一帧一帧的读取出来,然后再一帧一帧的把数据写入到另外一文件用来测试模拟的视频流是否正确
Input:  None
OutPut: None
Return: 0: success,none 0:error
Others:
Author: Wen Lee
Date:   2018.07.15
*********************************************************/
int TestH264ReadAndWrite(void)
{int l_s32Ret = 0;int l_DateLen = 0;NALU_S * l_pstNalu = NULL;/**创建h264输出文件**/l_s32Ret = CreatOuth264File();if(0!=l_s32Ret){return -1;}/**初始化视频流数据**/l_pstNalu = InitVideoStream(MAX_VIDEO_SIZE);if(NULL == l_pstNalu){return -1;}/**读取写入整个文件**/while(1){l_DateLen = ReadOneFrameVideo(l_pstNalu);if(l_DateLen > 0){l_s32Ret = fwrite(l_pstNalu->buf,1,l_DateLen,g_out_h264_fd);if(l_s32Ret!=l_DateLen){printf("%s %d write file error \n",__FILE__,__LINE__);}}else{break;}}/**关闭视频流文件**/CloseVideoStream(l_pstNalu);fclose(g_out_h264_fd);return 0;
}/********************************************************
Function: TestH264ToAVI
Description:AVI文件里只封装视频文件
Input:  None
OutPut: None
Return: 0: success,none 0:error
Others:
Author: Caibiao Lee
Date:   2017-12-14
*********************************************************/
int TestH264ToAVI(void)
{int l_s32Ret = 0;int l_DateLen = 0;int l_u32WriteLen = 0;unsigned int l_u32MoviLen = 0;unsigned int l_u32AVIWriteLen = 0;AVI_PARA_S stPara;   NALU_S * l_pstNalu = NULL;unsigned char *l_pu8Buff = NULL;/**创建AVI文件**/l_s32Ret = CreatAVIFile();if(0!=l_s32Ret){return -1;}/**初始化视频流数据**/l_pstNalu = InitVideoStream(MAX_VIDEO_SIZE);if(NULL == l_pstNalu){return -1;}/**视频参数**/stPara.bVenc        = true;  stPara.u32Width     = 640;stPara.u32Height   = 360; stPara.u32Gop       = 90;stPara.u64fps     = 25;stPara.pCompressor  = "h264";/**音频参数**/stPara.bAenc        = false; //不使用音频 stPara.u32Format  = 1;stPara.u32Rate     = 8000;  stPara.u32Bits        = 16; stPara.u32Mp3Rate    = 0;/**设置是否使用索引**/stPara.s8HasIndex = 0; /**初始化AVI文件头结构**/RIFF_WriteAviHeader(stPara,g_out_avi_fd);l_pu8Buff = (unsigned char*)malloc(MAX_VIDEO_SIZE);if(NULL==l_pu8Buff){printf("%s %d malloc error \n",__FILE__,__LINE__);return -1;}while(1){l_DateLen = ReadOneFrameVideo(l_pstNalu);if(l_DateLen > 0){/**设置视频标签**/memcpy(l_pu8Buff,AVI_VIDEO_FLAG,4);/**数据对齐**/l_u32WriteLen = AVICalcAlign(l_DateLen,4);/**设置长度**/l_pu8Buff[4] = (l_u32WriteLen)&0xff;l_pu8Buff[5] = (l_u32WriteLen>> 8)&0xff;l_pu8Buff[6] = (l_u32WriteLen>>16)&0xff;l_pu8Buff[7] = (l_u32WriteLen>>24)&0xff;/**实际应该写入的长度**/l_u32WriteLen += 8 ;   /**4ByteFlag + 4ByteLen**/memcpy(l_pu8Buff + 8,l_pstNalu->buf,l_DateLen);l_s32Ret = fwrite(l_pu8Buff,1,l_u32WriteLen,g_out_avi_fd);if(l_s32Ret!=l_u32WriteLen){printf("%s %d write file error \n",__FILE__,__LINE__);}l_u32MoviLen +=l_u32WriteLen;}else{break ;}}/**更新AVI文件头结构中的长度值**/l_u32AVIWriteLen = ftell(g_out_avi_fd);/**RIFF文件中的度**/stPara.u32AviLen = l_u32AVIWriteLen - 8; /**4ByteRIFF + 4ByteLen**//**movi块的长度**/stPara.u32MoviLen = l_u32MoviLen;/**写入到头文件中**/RIFF_WriteAviHeader(stPara,g_out_avi_fd);return 0;
}/********************************************************
Function: TestH264AndPCMToAVI
Description:将h264视频流和PCM音频流封装到AVI文件中
Input:  None
OutPut: None
Return: 0: success,none 0:error
Others: 测试使用的音频是2Channel_44100_16bit.pcm音频采样率            = 44100,采样通道 = 2,位深度 = 16假设采样间隔            = 20ms,也就是每秒采集50次 一秒钟总的数据量 =      44100 * 2*16/8 = 176400 字节所以每帧音频数据大小 = 176400/50 = 3528 字节视频帧率是25 音频率是50,模拟媒体流时间不准确,所以使用存一帧视频再存两帧音频使音视频同步
Author: Wen Lee
Date:   2018-07-20
*********************************************************/
int TestH264AndPCMToAVI(void)
{int l_s32Ret = 0;int l_DateLen = 0;int l_u32WriteLen = 0;int l_u32AudioLen = 0;unsigned int l_u32AVIWriteLen = 0;AVI_PARA_S stPara;   NALU_S * l_pstNalu = NULL;unsigned char *l_pu8Buff = NULL;unsigned char l_arru8Audio[1024*4] = {0};AVI_INDEX_ENTRY_S l_stIndexEntry;/**创建AVI文件**/l_s32Ret = CreatAVIFile();if(0!=l_s32Ret){return -1;}/**初始化视频流数据**/l_pstNalu = InitVideoStream(MAX_VIDEO_SIZE);if(NULL == l_pstNalu){return -1;}/**打开音频流**/l_s32Ret = InitAudioStream();if(0!=l_s32Ret){return -1;}/**视频参数**/stPara.bVenc        = true;  stPara.u32Width     = 640;stPara.u32Height   = 360; stPara.u32Gop       = 90;stPara.u64fps     = 25;stPara.pCompressor  = "h264";/**音频参数**/stPara.bAenc        = true; //不使用音频 stPara.u32Format   = 1;stPara.u32Rate     = 44100;  stPara.u32Channels  = 2;stPara.u32Bits      = 16; stPara.u32Mp3Rate    = 0;stPara.u32AVChanNum = 2;/**设置是否使用索引**/stPara.s8HasIndex = 0; /**初始化AVI文件头结构**/RIFF_WriteAviHeader(stPara,g_out_avi_fd);l_pu8Buff = (unsigned char*)malloc(MAX_VIDEO_SIZE);if(NULL==l_pu8Buff){printf("%s %d malloc error \n",__FILE__,__LINE__);return -1;}while(1){/**读一帧视频数据**/l_DateLen = ReadOneFrameVideo(l_pstNalu);if(l_DateLen > 0){/**设置视频标签**/memcpy(l_pu8Buff,AVI_VIDEO_FLAG,4);/**数据对齐**/l_u32WriteLen = AVICalcAlign(l_DateLen,4);/**设置长度**/l_pu8Buff[4] = (l_u32WriteLen)&0xff;l_pu8Buff[5] = (l_u32WriteLen>> 8)&0xff;l_pu8Buff[6] = (l_u32WriteLen>>16)&0xff;l_pu8Buff[7] = (l_u32WriteLen>>24)&0xff;/**实际应该写入的长度**/l_u32WriteLen += 8 ;   /**4ByteFlag + 4ByteLen**/memcpy(l_pu8Buff + 8,l_pstNalu->buf,l_DateLen);l_s32Ret = fwrite(l_pu8Buff,1,l_u32WriteLen,g_out_avi_fd);if(l_s32Ret!=l_u32WriteLen){printf("%s %d write file error \n",__FILE__,__LINE__);}}else{break ;}/**读两帧音频数据**/for(int i=0;i<2;i++){l_u32AudioLen = 3528;l_DateLen = ReadOneFrameAudio(&l_arru8Audio[8],l_u32AudioLen);if(l_DateLen > 0){/**设置视频标签**/memcpy(l_arru8Audio,AVI_AUDIO_FLAG,4);/**数据对齐**/l_u32AudioLen = AVICalcAlign(l_DateLen,4);/**设置长度**/l_arru8Audio[4] = (l_u32AudioLen)&0xff;l_arru8Audio[5] = (l_u32AudioLen>> 8)&0xff;l_arru8Audio[6] = (l_u32AudioLen>>16)&0xff;l_arru8Audio[7] = (l_u32AudioLen>>24)&0xff;  l_u32AudioLen += 8;l_s32Ret = fwrite(l_arru8Audio,1,l_u32AudioLen,g_out_avi_fd);if(l_s32Ret!=l_u32AudioLen){printf("%s %d write file error \n",__FILE__,__LINE__);}}}}/**更新AVI文件头结构中的长度值**/l_u32AVIWriteLen = ftell(g_out_avi_fd);stPara.u32MoviLen = l_u32AVIWriteLen;/**RIFF文件中的度**/stPara.u32AviLen = l_u32AVIWriteLen - 8; /**4ByteRIFF + 4ByteLen**//**写入到头文件中**/RIFF_WriteAviHeader(stPara,g_out_avi_fd);CloseVideoStream(l_pstNalu);CloseAudioStream();return 0;
}/********************************************************
Function: TestH264AndPCMToAVI
Description:合成音频视频流,同时添加索引表
Input:  None
OutPut: None
Return: 0: success,none 0:error
Others: 在TestH264AndPCMToAVI的基础上添加索引表
Author: Wen Lee
Date:   2018-07-20
*********************************************************/
int TestAVIIndex(void)
{int l_s32Ret = 0;int l_DateLen = 0;int l_u32WriteLen = 0;int l_u32AudioLen = 0;unsigned int l_u32AVIWriteLen = 0;AVI_PARA_S stPara;   NALU_S * l_pstNalu = NULL;unsigned char *l_pu8Buff = NULL;unsigned char l_arru8Audio[1024*4] = {0};AVI_INDEX_ENTRY_S l_stIndexEntry;WRITE_AVI_INDEX_S *pstWriteAviIndex;/**创建AVI文件**/l_s32Ret = CreatAVIFile();if(0!=l_s32Ret){printf("%s %d init error \n",__FILE__,__LINE__);return -1;}/**初始化视频流数据**/l_pstNalu = InitVideoStream(MAX_VIDEO_SIZE);if(NULL == l_pstNalu){printf("%s %d init error \n",__FILE__,__LINE__);return -1;}/**打开音频流**/l_s32Ret = InitAudioStream();if(0!=l_s32Ret){printf("%s %d init error \n",__FILE__,__LINE__);return -1;}/**为索引表分配空间**/pstWriteAviIndex = (WRITE_AVI_INDEX_S *)malloc(sizeof(WRITE_AVI_INDEX_S));if(NULL==pstWriteAviIndex){printf("%s %d malloc error \n",__FILE__,__LINE__);return -1;}/**视频参数**/stPara.bVenc        = true;  stPara.u32Width     = 640;stPara.u32Height    = 360; stPara.u32Gop       = 90;stPara.u64fps     = 25;stPara.pCompressor  = "h264";/**音频参数**/stPara.bAenc        = true; //不使用音频 stPara.u32Format   = 1;stPara.u32Rate     = 44100;  stPara.u32Channels  = 2;stPara.u32Bits      = 16; stPara.u32Mp3Rate    = 0;stPara.u32AVChanNum = 2;/**!!设置是否使用索引 !!**/stPara.s8HasIndex = 1; /**初始化AVI文件头结构**/RIFF_WriteAviHeader(stPara,g_out_avi_fd);l_pu8Buff = (unsigned char*)malloc(MAX_VIDEO_SIZE);if(NULL==l_pu8Buff){printf("%s %d malloc error \n",__FILE__,__LINE__);return -1;}while(1){/**读一帧视频数据**/l_DateLen = ReadOneFrameVideo(l_pstNalu);if(l_DateLen > 0){/**设置视频标签**/memcpy(l_pu8Buff,AVI_VIDEO_FLAG,4);/**数据对齐**/l_u32WriteLen = AVICalcAlign(l_DateLen,4);/**设置长度**/l_pu8Buff[4] = (l_u32WriteLen)&0xff;l_pu8Buff[5] = (l_u32WriteLen>> 8)&0xff;l_pu8Buff[6] = (l_u32WriteLen>>16)&0xff;l_pu8Buff[7] = (l_u32WriteLen>>24)&0xff;/**实际应该写入的长度**/l_u32WriteLen += 8 ;   /**4ByteFlag + 4ByteLen**/memcpy(l_pu8Buff + 8,l_pstNalu->buf,l_DateLen);l_s32Ret = fwrite(l_pu8Buff,1,l_u32WriteLen,g_out_avi_fd);if(l_s32Ret!=l_u32WriteLen){printf("%s %d write file error \n",__FILE__,__LINE__);}l_stIndexEntry.u32Flags = 0x00;/**判断是否为关键帧 关键帧为0x10 其它为0x00   **/if((0==l_pu8Buff[8])&&(0==l_pu8Buff[9])&&(1==l_pu8Buff[10])&&(0x05==(l_pu8Buff[11]&0x1F))){l_stIndexEntry.u32Flags = 0x10;}if((0==l_pu8Buff[8])&&(0==l_pu8Buff[9])&&(0==l_pu8Buff[10])&&(1==l_pu8Buff[11])&&(0x05==(l_pu8Buff[12]&0x1F))){l_stIndexEntry.u32Flags = 0x10;}l_stIndexEntry.u32ChunkId = 0x63643030; /**'00dc'**/l_stIndexEntry.u32Size    = l_u32WriteLen - 8;RIFF_WriteAviIndex(l_stIndexEntry,pstWriteAviIndex);}else{break ;}/**读两帧音频数据**/for(int i=0;i<2;i++){l_u32AudioLen = 3528;l_DateLen = ReadOneFrameAudio(&l_arru8Audio[8],l_u32AudioLen);if(l_DateLen > 0){/**设置视频标签**/memcpy(l_arru8Audio,AVI_AUDIO_FLAG,4);/**数据对齐**/l_u32AudioLen = AVICalcAlign(l_DateLen,4);/**设置长度**/l_arru8Audio[4] = (l_u32AudioLen)&0xff;l_arru8Audio[5] = (l_u32AudioLen>> 8)&0xff;l_arru8Audio[6] = (l_u32AudioLen>>16)&0xff;l_arru8Audio[7] = (l_u32AudioLen>>24)&0xff;   l_u32AudioLen += 8;l_s32Ret = fwrite(l_arru8Audio,1,l_u32AudioLen,g_out_avi_fd);if(l_s32Ret!=l_u32AudioLen){printf("%s %d write file error \n",__FILE__,__LINE__);}l_stIndexEntry.u32ChunkId = 0x64773130; /**'01wd'**/l_stIndexEntry.u32Flags = 0x00;l_stIndexEntry.u32Size = l_u32AudioLen - 8;RIFF_WriteAviIndex(l_stIndexEntry,pstWriteAviIndex);}}}/**更新AVI文件头结构中的长度值**/l_u32AVIWriteLen = ftell(g_out_avi_fd);stPara.u32MoviLen = l_u32AVIWriteLen;/**写索引表到AVI文件中去***/RIFF_WriteAviIndexToFile(pstWriteAviIndex,g_out_avi_fd);/**更新AVI文件头结构中的长度值**/l_u32AVIWriteLen = ftell(g_out_avi_fd);/**RIFF文件中的度**/stPara.u32AviLen = l_u32AVIWriteLen - 8; /**4ByteRIFF + 4ByteLen**//**写入到头文件中**/RIFF_WriteAviHeader(stPara,g_out_avi_fd);/**关闭资源**/CloseVideoStream(l_pstNalu);CloseAudioStream();return 0;
}int main(void)
{int l_s32Ret = 0;printf("Hello World \n");/**h246文件测试**///l_s32Ret = TestH264ReadAndWrite();/**只封装视频数据*///l_s32Ret = TestH264ToAVI();/**同时封装音频和视频数据**///l_s32Ret = TestH264AndPCMToAVI();/**添加索引**/l_s32Ret = TestAVIIndex();return 0;
}

编译运行最后生成test.avi文件,使用暴风播放器播放效果如下:

完整的工程下载地址:https://download.csdn.net/download/li_wen01/10555981

需要注意的事项有:

1.一个音频文件也就是一个音频流就是一轨音频,这一轨音频可以是单通道的,也可以是立体的(双通道或是多通道)

2.注意RIFF的每一个块的长度,它都是不包含块标签4字节和长度本身4字节的长度

3.使用AVI格式查看工具查看数据格式是否正常

相关文章:

AVI音视频封装格式学习(一)——微软RIFF文件格式摘要

AVI音视频封装格式学习(二)——AVI RIFF文件参考

AVI音视频封装格式学习(三)——AVI 数据结构解析

AVI音视频封装格式学习(四)——linux系统C语言AVI格式音视频封装应用

 

===============================2022.08.28===============================

博客附件资料下载及博客内容更新,可以通过下面博客获取

li_wen01嵌入式开发相关博客更新目录及资料下载说明

liwen01 2022.08.28 日更新 

===============================2022.08.28===============================

AVI音视频封装格式学习(四)——linux系统C语言AVI格式音视频封装应用相关推荐

  1. linux系统硬盘 转换格式,怎么在LINUX系统里修改硬盘格式呢?

    满意答案 q471507130 2013.06.18 采纳率:54%    等级:13 已帮助:10117人 第一个ide设备是hda,第二个是hdb.依此类推 我们一般主板上有两个IDE接口,一共可 ...

  2. linux 下视频编辑软件下载,在Linux系统中下载和安装Shotcut视频编辑工具

    Shotcut是一款免费的.跨平台的非线性视频编辑工具,它使用MLT框架并利用FFMepg的功能构建,可应用在Linux操作系统下.当前Shotcut 20.02.17版本已发布下载,它增加了一些显着 ...

  3. AVI音视频封装格式学习(五)——h265与PCM合成AVI文件

    不知道是处于版权收费问题还是什么原因,H265现在也并没有非常广泛的被普及.将h265数据合成AVI的资料现在在网上也基本上没有.使用格式化工厂工具将h265数据封装成AVI格式,发现它在封装的时候其 ...

  4. ElasticSearch学习(四)——Linux 单节点部署

    文章名称 地址 ElasticSearch学习(一)--概述 前往 ElasticSearch学习(二)--索引.文档简单操作 前往 ElasticSearch学习(三)--Windows 集群部署 ...

  5. Linux学习之三-Linux系统的一些重要配置文件

    Linux学习之三-Linux系统的一些重要配置文件 1.网卡配置文件 /etc/sysconfig/network-scripts/ifcfg-eth0 说明: DEVICE=eth0        ...

  6. SoC学习篇—Linux 系统搭建

    SoC学习篇-Linux 系统搭建 一.制作.dtb与.rbf文件 1.编译硬件工程 2.文件制作 二.启动操作系统内核 1.编译uboot 2.生成preloader-mkpimage.bin文件 ...

  7. Linux系统中常见文件系统格式

    Linux系统中常见文件系统格式 Windows常用的分区格式有三种,分别是FAT16.FAT32.NTFS格式. 在Linux操作系统里有Ext2.Ext3.Linux swap和VFAT四种格式. ...

  8. 【北京迅为iMX6ULL】嵌入式学习之Linux系统编程视频教程

    什么是Linux系统编程? Linux系统编程也叫Linux下的高级编程,是介于应用层和驱动层之间的. 学习了哪些知识后可以学习Linux系统编程? C语言基础.Linux基本操作命令 怎么学习Lin ...

  9. 浅谈学习掌握linux系统的优势

    导读 Linux系统让我们懂得了共享.开放.自由可以让人类生活的更加美好,开源精神是一种让每个从事Linux行业的技术人员从骨子里自豪的情怀,开源产品的兴盛受益于开源社区的强壮根基.Linux真的给了 ...

最新文章

  1. Java数据结构 反转链表
  2. oracle rman 架构图,图解Oracle RMAN备份入门
  3. 程序员面试题精选100题(38)-输出1到最大的N位数[算法]
  4. Crawler之Scrapy:Scrapy简介、安装、使用方法之详细攻略
  5. vue根据url获取内容axios_vue+vuex+axios从后台获取数据存入vuex,组件之间共享数据...
  6. 表格过滤器_不用记账软件也可以记录支出明细,这个在线协同表格很方便
  7. Sentinel降级_RT_分布式系统集群限流_线程数隔离_削峰填谷_流量控制_速率控制_服务熔断_服务降级---微服务升级_SpringCloud Alibaba工作笔记0039
  8. VS 2008 和 .NET 3.5 Beta 2 发布了
  9. 树莓派3B的WiFi中文乱码及搜索不到附近的WiFi_解决方案:
  10. java实例摘要(三)
  11. Spark Streaming的玫瑰与刺
  12. java基础题目的网站_Java基础题目
  13. Android studio :Android finished with non-zero exit value 1
  14. pb模型文件与.pbtxt配置不匹配导致OpenCV调用dnn模块出错(Mask R-CNN为例)
  15. 教你三秒钟将电脑速度提高三倍
  16. 图片放大后怎么把图片变清晰?
  17. 神经网络模型-ART 自适应共振网络
  18. mysql8.0.21官网安装步骤_MySQL8.0.21安装步骤及出现问题解决方案
  19. 如何实现开发一个企业认证开发版微信服务号的详细步骤
  20. 合宙模块LUA相关资料汇总

热门文章

  1. 爱普生Epson Stylus SX235W 一体机驱动
  2. 还在为制作大屏苦恼?这个Smartbi神器才是你的出路
  3. c++使用制表符\t
  4. elasticsearch 设置seed hosts
  5. c语言电报,[编程入门]电报加密-题解(C语言代码)
  6. ECharts之类型3D(map,bar3D,scatter3D)
  7. 数字信号处理实验4:用窗口法设计FIR滤波器
  8. 微博数据分析工具限时福利!购买西瓜微数加送会员时长 ,最多加赠1个月!
  9. C语言实现万年历系统
  10. iOS系统日历多了莫名其妙的事项