使用FFMPEG从MP4封装中提取视频流到.264文件

本文介绍了一条ffmpeg命令,用于将MP4文件转换为h.264格式的annexB封装。命令包括输入文件、拷贝编码方式、转换格式和输出文件名。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

命令行:

ffmpeg -i 20130312_133313.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 20130312_133313.264

说明:

 -i 20130312_133313.mp4 :是输入的MP4文件

-codec copy:从MP4封装中进行拷贝

-bsf: h264_mp4toannexb:从MP4拷贝到annexB封装

-f h264:采用h.264格式

20130312_133313.264:输出的文件名称


<think>我们使用ROS bag中的H.264压缩图像消息,将其提取并保存为MP4文件。我们可以使用PyAV或OpenCV来实现。 步骤: 1. 读取ROS bag文件,找到包含H.264压缩图像的话题。 2. 初始化视频写入器(使用PyAV或OpenCV的VideoWriter)。 3. 对每个消息进行解码(如果需要)然后写入视频帧。但注意:有些方法可以直接写入H.264数据流而不需要逐帧解码再编码(这样会损失质量且慢),但通常我们为了确保正确性,会解码后再编码(或者如果原始数据是H.264流,我们可以直接写入,但需要处理容器格式)。 4. 保存为MP4文件。 然而,直接提取H.264裸流并封装MP4是更高效的方法。但ROS的CompressedImage消息可能不包含完整的流信息(如SPS/PPS),所以我们需要确保在写入文件前添加这些信息。 这里提供两种方法: 方法1:使用PyAV直接封装原始H.264数据(如果数据是完整的帧数据,包括关键帧) 方法2:逐帧解码再编码(不推荐,因为会损失质量且慢,但作为备选) 我们优先使用方法1,但需要确保从ROS消息中提取出必要的参数集(SPS和PPS)。如果消息中没有,我们可能需要从初始的几个数据包中提取。 注意:ROS的CompressedImage消息可能不包含SPS/PPS,因此我们需要在开始时手动添加。 根据ROS的文档,有些驱动会在每个关键帧前发送SPS/PPS,但并非所有都如此。因此,我们需要检查消息中是否有sps和pps字段(有些自定义的消息可能有,但标准的sensor_msgs/CompressedImage没有)。所以,我们可能需要从第一个帧中提取SPS/PPS,或者手动提供。 由于标准sensor_msgs/CompressedImage不包含SPS/PPS,我们可能需要通过其他方式获取。因此,更通用的方法是逐帧解码再编码。 考虑到效率和质量,如果原始数据是完整的H.264流(包括参数集),我们可以直接写入。否则,只能逐帧解码再编码。 这里我们展示两种方法: 方法1:直接封装(如果数据是完整的H.264流) - 我们需要将每个数据包(即CompressedImage.data)写入到MP4文件中,并确保在文件开头有SPS/PPS。但是,如果ROS bag中的H.264流是AVCC格式(包含SPS/PPS在开头),那么我们可以直接写入。但通常,ROS bag中的H.264流是Annex B格式(以0x00000001或0x000001开头)。而MP4容器需要AVCC格式。因此,我们需要换格式或者使用工具进行重新封装。 方法2:逐帧解码再编码(通用但慢) 由于方法1的复杂性,并且需要处理格式换,这里我们选择方法2,因为更通用,且可以确保正确性。 使用PyAV进行逐帧解码再编码: 步骤: 1. 读取bag文件中的每个CompressedImage消息。 2. 使用PyAV解码每一帧(得到AVFrame)。 3. 将解码后的帧编码为H.264(或直接复制,但这里我们重新编码会损失质量)?实际上,我们可以使用流复制,但这里我们无法避免解码再编码,因为原始数据是分段的。 但是,如果我们想避免重新编码,我们可以尝试将原始数据直接写入MP4容器,但需要换格式。这比较复杂。 考虑到时间,我们给出一个逐帧解码再编码的示例,虽然效率不高,但可以工作。 另一种思路:将所有的H.264数据按顺序写入一个.h264文件(Annex B格式),然后使用FFmpeg命令行工具将其换为MP4。这样我们可以利用FFmpeg换功能。 这里我们采用第二种思路: 步骤: 1. 提取所有消息的data字段,按时间顺序写入一个.h264文件(每个消息的数据直接写入)。 2. 使用FFmpeg.h264文件换为MP4。 示例代码: 步骤1:提取H.264裸流 ```python import rosbag import os def extract_h264_stream(bag_path, topic, output_file): bag = rosbag.Bag(bag_path, 'r') with open(output_file, 'wb') as f: for _, msg, _ in bag.read_messages(topics=[topic]): # 写入每个消息的数据 f.write(msg.data) bag.close() extract_h264_stream('input.bag', '/camera/image_raw/compressed', 'output.h264') ``` 步骤2:使用FFmpeg换(在命令行中) ```bash ffmpeg -i output.h264 -c copy output.mp4 ``` 但是,如果.h264文件缺少SPS/PPS,则换可能会失败。因此,我们需要确保在提取时,第一个关键帧前有SPS/PPS。如果ROS bag中没有在开始时发送SPS/PPS,我们可能需要手动添加。 在ROS中,通常每个关键帧前都会发送SPS/PPS(以0x00000001开头的NALU),所以直接拼接的数据应该是Annex B格式,可以被FFmpeg识别。 因此,我们使用上述方法。 如果换失败,我们可以尝试在FFmpeg命令中指定帧率和分辨率: ```bash ffmpeg -r 30 -s 1280x720 -i output.h264 -c copy output.mp4 ``` 在Python中,我们可以使用subprocess调用FFmpeg。 完整脚本(包括换): ```python import rosbag import subprocess def convert_bag_h264_to_mp4(bag_path, topic, output_mp4, frame_rate=30, resolution='1280x720'): # 先提取为临时h264文件 temp_h264 = 'temp.h264' with rosbag.Bag(bag_path, 'r') as bag: with open(temp_h264, 'wb') as f: for _, msg, _ in bag.read_messages(topics=[topic]): f.write(msg.data) # 使用FFmpeg换 cmd = [ 'ffmpeg', '-y', # 覆盖输出文件 '-r', str(frame_rate), '-s', resolution, '-i', temp_h264, '-c', 'copy', output_mp4 ] subprocess.run(cmd, check=True) # 删除临时文件(可选) # os.remove(temp_h264) # 使用示例 convert_bag_h264_to_mp4('test.bag', '/camera/image_raw/compressed', 'output.mp4', frame_rate=30, resolution='1280x720') ``` 注意:如果不知道帧率和分辨率,需要从ROS消息中获取。帧率可以通过消息间隔估算,分辨率则需要从消息中获取(如width和height,但CompressedImage消息中可能没有,需要从其他消息获取,或者事先知道)。 如果CompressedImage消息中没有width和height,我们可以尝试从第一帧解码得到,或者用户提供。 改进:我们可以从消息中获取分辨率(如果消息类型是sensor_msgs/CompressedImage,它没有直接包含分辨率,但通常会有其他话题发布相机信息,例如camera_info)。如果无法获取,则需要用户指定。 因此,我们建议用户指定帧率和分辨率。 如果用户无法确定,我们可以通过读取第一帧来获取分辨率(通过解码),但这样会增加复杂性。 这里我们假设用户知道这些参数。 另外,如果H.264流中已经包含了时间信息,我们也可以不指定帧率,让FFmpeg自动处理。但最好指定。 所以,我们的最终方案是: 1. 提取所有数据到临时.h264文件。 2. 调用FFmpeg换。 如果遇到错误,可能是由于缺少SPS/PPS,我们可以尝试在FFmpeg命令前添加一个步骤:在文件开头插入SPS/PPS(如果知道的话)。但通常Annex B格式每个关键帧前都有SPS/PPS,所以如果第一个帧是关键帧,那么开头就会有SPS/PPS。 因此,这个方法在大多数情况下有效。 现在,我们按照要求给出完整代码,并注意处理异常。 注意:需要安装ffmpeg,且可以在命令行中使用。 如果用户没有ffmpeg,则需要在脚本中处理。 我们也可以使用PyAV来封装,但这样代码更长。这里我们选择调用命令行ffmpeg。 最后,我们输出相关问题。</think>### 从 ROS Bag 提取 H.264 并保存为 MP4 的完整方案 #### 方法 1:使用 FFmpeg 直接换(推荐) ```python import subprocess import rosbag import os def convert_bag_h264_to_mp4(bag_path, topic, output_mp4, fps=30): """ 从 ROS bag 直接提取 H.264 流并MP4 :param bag_path: ROS bag 文件路径 :param topic: 图像话题名称 (e.g. '/camera/image/compressed') :param output_mp4: 输出 MP4 文件路径 :param fps: 视频帧率 (默认 30fps) """ # 步骤 1: 从 bag 文件提取原始 H.264 数据 temp_h264 = "temp_raw.h264" with rosbag.Bag(bag_path, 'r') as bag: with open(temp_h264, 'wb') as f: for _, msg, _ in bag.read_messages(topics=[topic]): f.write(msg.data) # 写入原始 H.264 数据 # 步骤 2: 使用 FFmpeg 换为 MP4 ffmpeg_cmd = [ 'ffmpeg', '-y', # 覆盖输出文件 '-r', str(fps), '-i', temp_h264, '-c:v', 'copy', # 直接复制视频流,避免重新编码 '-f', 'mp4', output_mp4 ] subprocess.run(ffmpeg_cmd, check=True) # 清理临时文件 os.remove(temp_h264) # 使用示例 convert_bag_h264_to_mp4( bag_path='/path/to/input.bag', topic='/camera/image_raw/compressed', output_mp4='output_video.mp4', fps=30 ) ``` #### 方法 2:使用 PyAV 处理(高级控制) ```python import av import rosbag import numpy as np def save_bag_h264_as_mp4(bag_path, topic, output_file, fps=30): """使用 PyAV 处理并保存为 MP4""" # 创建输出容器和视频流 container = av.open(output_file, mode='w') stream = container.add_stream('h264', rate=fps) with rosbag.Bag(bag_path, 'r') as bag: for _, msg, _ in bag.read_messages(topics=[topic]): # 创建 H.264 数据包 packet = av.Packet(msg.data) # 设置关键帧标记 (根据 ROS 消息头) packet.is_keyframe = msg.header.seq % 30 == 0 # 添加到容器 container.mux(packet) container.close() ``` #### 关键优化技巧: 1. **帧率自动检测**: ```python # 计算实际帧率 first_ts = None last_ts = None count = 0 with rosbag.Bag(bag_path, 'r') as bag: for _, msg, _ in bag.read_messages(topics=[topic]): if first_ts is None: first_ts = msg.header.stamp last_ts = msg.header.stamp count += 1 real_fps = count / ((last_ts - first_ts).to_sec()) ``` 2. **添加音频流**(如果存在): ```python ffmpeg_cmd = [ 'ffmpeg', '-i', temp_h264, '-i', extracted_audio.wav, # 从其他话题提取的音频 '-c:v', 'copy', '-c:a', 'aac', '-strict', 'experimental', output_mp4 ] ``` 3. **时间戳同步**: ```python # 在 FFmpeg 命令中添加时间戳元数据 ffmpeg_cmd = [ 'ffmpeg', '-copyts', # 保留原始时间戳 '-i', temp_h264, ... ] ``` #### 常见问题解决: 1. **绿屏/花屏问题**: - 确保添加 SPS/PPS 头:`ffmpeg -i input.h264 -c:v copy -bsf:v h264_mp4toannexb output.mp4` 2. **帧率不稳定**: ```python ffmpeg_cmd.extend([ '-vsync', 'vfr', # 可变帧率模式 '-enc_time_base', '1/1000' ]) ``` 3. **分辨率错误**: ```python # 从第一个消息获取分辨率 with rosbag.Bag(bag_path, 'r') as bag: _, first_msg, _ = next(bag.read_messages(topics=[topic])) width = first_msg.width height = first_msg.height ffmpeg_cmd.extend(['-s', f'{width}x{height}']) ``` ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值