ffmpeg学习准备之pgm格式文件的解析(流程版)

PGM格式文件的解析和读取

  • 概述
  • 辅助函数
    • 说明
    • 代码
  • P2格式创建和读取
    • 创建
    • 读取
  • P5格式
    • 创建
    • 读取
  • P2格式和P5格式文件的比较
  • 遇到的问题
  • 总结

概述

PGM是存储和交换图像数据的简单文件格式之一。网上有许多博客都有介绍,不多做介绍;只做记录自己学习的过程;
链接信息

辅助函数

说明

如果下面代码有什么问题请指出来;下面采用的是C语言

代码

peek函数(C语言是没有这个函数,自己写的,类似于C++里面的peek函数)

int peek(FILE *fp) {
	 // todo 判空未做
   // 保存当前文件指针的位置
    fpos_t pos;
    fgetpos(fp, &pos);
    int ch;
    // 读取文件指针当前指向的字符,文件指针的位置自动指向下一个位置
    // 类似于其它高级语言里面的迭代器
    ch = fgetc(fp);
    fsetpos(fp, &pos);
    return ch;
}

skip_comment函数(用来跳过换行和注释信息)

void skip_comment(FILE *fp) {
	 // 检测当前字符是注释开或者是换行,后面的判断是过滤多个空行
    while (peek(fp) == '#'||peek(fp) == '\n') {
    	  // 过滤注释信息,直到换行
				while (fgetc(fp) != '\n');
    }

}

swap_two_byte函数(用来交换双字节存放的值)

// 移位版
unsigned  short swap_two_byte(unsigned short data) {
    return ((0xff & data) << 8) | ((0xff00 & data) >> 8);
}
// 指针版
void swap_two_byte_pointer(unsigned short *ptr) {
    // 取出第一个字节数据
    unsigned char tmp = *((unsigned char *) ptr);
    *((unsigned char *) ptr) = *((unsigned char *) ptr + 1);
    *((unsigned char *) ptr + 1) = tmp;
}

P2格式创建和读取

创建

封装的方法

void pgm_p2_write(const char *fileName, int width, int height, int maxValue) {
    FILE *fp = fopen(fileName, "w");
    if (fp == nullptr) exit(-1);
    const char *comment = "#这是注释信息";
    // fputs是一个函数,具有的功能是向指定的文件写入一个字符串(不自动写入字符串结束标记符‘\0’)
    fputs("P2", fp);
    fputc('\n', fp);
    fprintf(fp,"%s\n",comment);
    fprintf(fp, "%d %d\n", width, height);
    // max value
    fprintf(fp, "%d", maxValue);
    fputc('\n', fp);
    // 数据
    for (int i = 0; i < width; ++i) {
        for (int j = 0; j < height; ++j) {
            fprintf(fp, "%d", (i + j) & maxValue);
        }
        fputc('\n', fp);
    }

    fclose(fp);

}

测试

int main(){
    pgm_p2_write("pgm2.pgm", 30, 30, 50);
}

生成文件结果

ffmpeg学习准备之pgm格式文件的解析(流程版)_第1张图片ffmpeg学习准备之pgm格式文件的解析(流程版)_第2张图片

读取

封装方法

void pgm_p2_read(const char *fileName) {
    FILE *fp = fopen(fileName, "r");
    char buf[1024];
    fgets(buf, 1024, fp);
    printf("协议:%s", buf);
    // 跳过空格或注释
    skip_comment(fp);
    int width, height;
    fscanf(fp, "%d %d", &width, &height);
    printf("%d %d\n", width, height);
    // 跳过空格或注释
    skip_comment(fp);
    int max;
    fscanf(fp, "%d", &max);
    printf("%d\n", max);
    // 跳过空格或注释
    skip_comment(fp);
    unsigned data[width][height];
    for (int i = 0; i < width; ++i) {
        for (int j = 0; j < height; ++j) {
            fscanf(fp, "%d", &data[i][j]);
            printf("%d ", data[i][j]);
        }
        printf("\n");
    }


    fclose(fp);

}

P5格式

创建

封装方法

void pgm_p5_write(const char *fileName, int width, int height, int maxValue) {
    FILE *fp = fopen(fileName, "wb+");
    if (fp == nullptr) exit(-1);
    fprintf(fp, "P5\n");
    fprintf(fp, "%d %d\n", width, height);
    // max value
    fprintf(fp, "%d\n", maxValue);
    // 判断数据写入占用的字节数,只做1字节和2字节的
    int dataSize = 1;
    if (maxValue > UCHAR_MAX) {
        dataSize = 2;
    }
    for (int i = 0; i < width; ++i) {
        for (int j = 0; j < height; ++j) {
            unsigned short value = (i + j) % maxValue == 0 ? maxValue : (i + j) % maxValue;
            if (dataSize == 2) {
                value = swap_two_byte(value);
            }
//            printf("%d ", value);
            fwrite(&value, dataSize, 1, fp);
        }
//        printf("\n");
    }
    fclose(fp);
}

读取

封装方法

void pgm_p5_read(const char *fileName) {
    FILE *fp = fopen(fileName, "rb");
    char buf[1024];
    fscanf(fp, "%s", buf);
    printf("%s\n", buf);
    skip_comment(fp);
    skip_comment(fp);
    int width, height;
    fscanf(fp, "%d %d", &width, &height);
    printf("%d %d\n", width, height);
    skip_comment(fp);
    unsigned max = 0;
    fscanf(fp, "%d", &max);
    printf("%d\n", max);
    if (peek(fp) == '\n') {
        fgetc(fp);
    }
    // data
    unsigned num;
    if (max > UCHAR_MAX) {
        num = 2;
    } else {
        num = 1;
    }
    for (int i = 0; i < width; ++i) {
        for (int j = 0; j < height; ++j) {
            unsigned short data;
            fread(&data, num, 1, fp);
//          if (num == 2) data = swap_two_byte(data); // 移位操作
            if (num == 2) swap_two_byte_pointer(&data); // 地址操作
            printf("%d ", data);
        }
        printf("\n");
    }
    fclose(fp);
}

P2格式和P5格式文件的比较

测试代码

int main() {
    pgm_p2_write("p2_file.pgm", 100, 200, 257);
    pgm_p5_write("p5_file.pgm", 100, 200, 257);
}

结果

ffmpeg学习准备之pgm格式文件的解析(流程版)_第3张图片

遇到的问题

  1. 在P5格式文件的读写中,在存储多字节数据的时候是有一个字节序的写入的问题;所以要么写入多字节数据的时候,按照要求一个字节的写入;要么就是直接对字节存放的数据进行交换,然后一次写入数据(读取的时候也是需要根据写入的数据来还原)

总结

  1. 这篇博客是用来学习根据PGM文件格式来联系二进制数据的读取和解析
  2. 准备在下一篇博客对代码结构进行封装和两种格式之间进行转换
  3. 有什么问题请指出(虚心求教)

你可能感兴趣的:(ffmpeg,音视频)