/*
***********************************************************
* A simple program to copy H264 video from a file to RTMP *
* Author: <yanhongkui@yeah.net> *
* Dated: 2024-02-20 *
* MIT License *
* Compile: gcc -Wall -g -o h264-2-rtmp.copy h264-2-rtmp.copy.c -lavformat -lavcodec -lavutil -lswscale -lz -lm -lpthread
***********************************************************
*/
#include <stdio.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
// 定义帧信息结构体
typedef struct {
int start;
int end;
int length;
} FrameInfo;
/**
* @brief 查找 SPS 和 PPS
*
* 从给定的数据缓冲区中查找 SPS(序列参数集)和 PPS(图像参数集)。
*
* @param buf 数据缓冲区指针
* @param len 数据缓冲区长度
* @param sps_info SPS 信息结构体指针
* @param pps_info PPS 信息结构体指针
*/
void find_sps_pps(uint8_t* buf, int len, FrameInfo* sps_info, FrameInfo* pps_info) {
int i = 0;
while (i < len - 3) {
if (buf[i] == 0x00 && buf[i + 1] == 0x00 && buf[i + 2] == 0x00 && buf[i + 3] == 0x01) {
int nal_type = buf[i + 4] & 0x1F;
if (nal_type == 7 && sps_info->start == -1) {
sps_info->start = i;
} else if (nal_type == 8 && sps_info->start != -1 && pps_info->start == -1) {
sps_info->end = i - 1;
sps_info->length = sps_info->end - sps_info->start + 1;
pps_info->start = i;
} else if (nal_type != 7 && nal_type != 8 && sps_info->start != -1 && pps_info->start != -1) {
pps_info->end = i - 1;
pps_info->length = pps_info->end - pps_info->start + 1;
break;
}
i += 4; // Move to the next NAL unit
} else if (buf[i] == 0x00 && buf[i + 1] == 0x00 && buf[i + 2] == 0x01) {
int nal_type = buf[i + 3] & 0x1F;
if (nal_type == 7 && sps_info->start == -1) {
sps_info->start = i;
} else if (nal_type == 8 && sps_info->start != -1 && pps_info->start == -1) {
sps_info->end = i - 1;
sps_info->length = sps_info->end - sps_info->start + 1;
pps_info->start = i;
} else if (nal_type != 7 && nal_type != 8 && sps_info->start != -1 && pps_info->start != -1) {
pps_info->end = i - 1;
pps_info->length = pps_info->end - pps_info->start + 1;
break;
}
i += 3; // Move to the next NAL unit
} else {
i++;
}
}
}
/**
* @brief 主函数入口
*
* 主函数入口,用于执行 H.264 视频流的推流操作。
*
* @param argc 命令行参数个数
* @param argv 命令行参数数组
*
* @return 返回执行结果,成功为 0,失败为 -1
*/
int main(int argc, char *argv[]) {
const char *input_filename = "./test.h264";
const char *output_url = "rtmp://127.0.0.1:1935/live/test";
/* 读取第一帧的SPS和PPS */
FILE* fp = fopen(input_filename, "rb");
if (fp == NULL) {
printf("open file %s failed\n", input_filename);
return -1;
}
int ret = 0;
uint8_t sps_pps_buff[4096] = {0};
int first_frame_size = 1024;
ret = fread(sps_pps_buff, 1, first_frame_size, fp);
if (ret != first_frame_size) {
printf("Read first frame from file %s failed\n", input_filename);
fclose(fp);
exit(1);
}
fclose(fp);
FrameInfo sps_info = {-1, -1, 0};
FrameInfo pps_info = {-1, -1, 0};
find_sps_pps(sps_pps_buff, first_frame_size, &sps_info, &pps_info);
if (sps_info.start != -1 && sps_info.end != -1) {
printf("SPS Frame: Start = %d, End = %d, Length = %d\n",
sps_info.start, sps_info.end, sps_info.length);
} else {
printf("SPS Frame: Not found\n");
return -1;
}
if (pps_info.start != -1 && pps_info.end != -1) {
printf("PPS Frame: Start = %d, End = %d, Length = %d\n",
pps_info.start, pps_info.end, pps_info.length);
} else {
printf("PPS Frame: Not found\n");
return -1;
}
int spspps_len = sps_info.length + pps_info.length;
/* */
avformat_network_init();
AVFormatContext *input_format_context = NULL;
AVFormatContext *output_format_context = NULL;
// Open input H.264 file
if (avformat_open_input(&input_format_context, input_filename, NULL, NULL) < 0) {
fprintf(stderr, "Could not open input file\n");
return 1;
}
if (avformat_find_stream_info(input_format_context, NULL) < 0) {
fprintf(stderr, "Could not find stream information\n");
return 1;
}
// Open output RTMP stream
if (avformat_alloc_output_context2(&output_format_context, NULL, "flv", output_url) < 0) {
fprintf(stderr, "Could not create output context\n");
return 1;
}
AVStream *output_stream = avformat_new_stream(output_format_context, NULL);
if (!output_stream) {
fprintf(stderr, "Failed to allocate output stream\n");
return 1;
}
// 必须手动设置这些参数
output_stream->codecpar->codec_tag = 0;
output_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
output_stream->codecpar->codec_id = AV_CODEC_ID_H264;
output_stream->codecpar->bit_rate = 400000;
output_stream->codecpar->width = 640;
output_stream->codecpar->height = 480;
output_stream->codecpar->format = AV_PIX_FMT_YUV420P;
// 降低推流延时
output_stream->codecpar->video_delay = 0;
// 必须设置 extradata, 否则在h264的帧复制模式下流无法播放
output_stream->codecpar->extradata_size = spspps_len;
output_stream->codecpar->extradata = (uint8_t*)av_malloc(spspps_len + AV_INPUT_BUFFER_PADDING_SIZE);
if (output_stream->codecpar->extradata == NULL) {
fprintf(stderr, "Could not allocate extradata\n");
}
memcpy(output_stream->codecpar->extradata, sps_pps_buff, spspps_len);
if (avio_open(&output_format_context->pb, output_url, AVIO_FLAG_WRITE) < 0) {
fprintf(stderr, "Could not open output URL\n");
return 1;
}
AVDictionary *opts = NULL;
av_dict_set(&opts, "flvflags", "add_keyframe_index", 0);
ret = avformat_write_header(output_format_context, &opts);
if (ret < 0) {
fprintf(stderr, "Could not write header\n");
}
av_dict_free(&opts);
AVPacket packet;
av_init_packet(&packet);
int frame_rate = 25;
int frame_interval_in_us = (1000000 / frame_rate);
int64_t pts = 0;
int64_t pkt_cnt = 0;
printf("Push rtmp start ...\n");
while (av_read_frame(input_format_context, &packet) >= 0) {
printf("frame=%ld: Read a packet from stream : packet.size = %d\n",
pkt_cnt++, packet.size);
packet.stream_index = 0;
packet.pts = pts;
packet.dts = pts;
//av_interleaved_write_frame(output_format_context, &packet);
av_write_frame(output_format_context, &packet);
pts += (frame_interval_in_us/1000);
av_usleep( frame_interval_in_us); // Delay for 1/25 second
av_packet_unref(&packet);
}
printf("Push rtmp over!\n");
// 刷新输出流
//av_interleaved_write_frame(output_format_context, NULL);
av_write_frame(output_format_context, NULL);
avio_flush(output_format_context->pb);
// 关闭输出流
avio_close(output_format_context->pb);
if (output_format_context) {
avformat_free_context(output_format_context);
}
//av_write_trailer(output_format_context);
// 释放资源
avformat_close_input(&input_format_context);
avformat_network_deinit();
printf("Game over!\n");
exit(0);
}