音视频学习笔记 02 读取数据
2025年6月28日 13:08
在笔记 01 的代码里读取数据可能会出错。
av_read_frame()
返回 -35
(即 AVERROR(EAGAIN)
或 AVERROR_EOF
)表示当前无法读取到数据包(packet),但未来可能可以。这通常发生在以下几种情况:
1. 非阻塞模式下数据未就绪
-
如果你将
AVFormatContext
设置为 非阻塞模式(通过AVFMT_FLAG_NONBLOCK
),av_read_frame()
可能会立即返回EAGAIN
(即-35
),表示当前没有可读的数据包,但稍后重试可能会成功。 -
解决方法:
- 如果是故意使用非阻塞模式,需要循环重试(或其他逻辑处理)。
- 如果不需要非阻塞模式,确保未设置
AVFMT_FLAG_NONBLOCK
。
2. 流结束(EOF)
-
如果已经读取完所有数据包(如文件末尾或直播流中断),
av_read_frame()
可能返回EAGAIN
或EOF
。 -
解决方法:
- 检查
fmt_ctx->pb->eof_reached
或pkt.flags
是否包含AV_PKT_FLAG_EOF
。 - 如果是正常结束,可以关闭上下文或重新初始化。
- 检查
3. 输入流格式问题
-
某些特殊格式(如实时流、网络流)可能需要更多初始化时间,或者数据未及时到达。
-
解决方法:
- 确保输入源正常(如文件路径正确、网络流可访问)。
- 检查
fmt_ctx
是否成功打开(avformat_open_input()
返回0
)。
4. 编码器/复用器未正确初始化
-
如果
fmt_ctx
是输出上下文(例如用于编码或复用),可能需要先写入头信息(avformat_write_header()
)。 -
解决方法:
- 确保正确初始化输出上下文。
调试步骤
-
检查错误码的具体含义:
char errbuf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(ret, errbuf, sizeof(errbuf)); fprintf(stderr, "av_read_frame failed: %s\n", errbuf);
输出错误详情(如 "Resource temporarily unavailable" 或 "End of file")。
-
验证输入源:
- 确认
fmt_ctx
已通过avformat_open_input()
成功打开。 - 检查
fmt_ctx->streams
是否包含有效的流(如fmt_ctx->nb_streams > 0
)。
- 确认
-
重试逻辑:
- 如果是非阻塞模式或实时流,可能需要循环调用
av_read_frame()
直到返回0
。
- 如果是非阻塞模式或实时流,可能需要循环调用
示例代码(处理非阻塞情况)
AVPacket pkt;
av_init_packet(&pkt);
while ((ret = av_read_frame(fmt_ctx, &pkt)) == AVERROR(EAGAIN)) {
// 等待或处理其他任务(如非阻塞模式)
usleep(1000); // 避免忙等待
}
if (ret < 0 && ret != AVERROR_EOF) {
// 真实错误
fprintf(stderr, "Error reading packet: %s\n", av_err2str(ret));
} else if (ret == AVERROR_EOF) {
// 正常结束
printf("Reached end of file.\n");
} else {
// 成功读取到数据包
// 处理 pkt...
av_packet_unref(&pkt);
}
如果问题仍存在,请提供更多上下文代码(如 fmt_ctx
的初始化部分)。
上面的代码 和 解释 由 deepseek 给出。
修改后的代码:
ViewController
//
// ViewController.swift
// myapp
//
// Created by mac on 2025/6/23.
//
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.setFrameSize(NSSize(width: 320, height: 240))
let btn = NSButton.init(title: "Button", target: nil, action: nil)
btn.title = "Hello"
btn.frame = NSRect(
x: 320 / 2 - 40,
y: 240 / 2 - 15,
width: 80,
height: 30
)
btn.bezelStyle = .rounded
btn.setButtonType(.pushOnPushOff)
// callback
btn.target = self
btn.action = #selector(myFunc)
self.view.addSubview(btn)
}
@objc
func myFunc() {
record_audio()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
bridge 文件 myapp/myapp/myapp-Bridging-Header.h
//
// Use this file to import your target's public headers that you would like to
// expose to Swift.
//
#import "testc.h"
c 头文件
//
// testc.h
// myapp
//
// Created by mac on 2025/6/23.
//
#ifndef testc_h
#define testc_h
#include "libavcodec/avcodec.h"
#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include <stdio.h>
#include <unistd.h>
void record_audio(void);
#endif /* testc_h */
c 代码体
//
// testc.c
// myapp
//
// Created by mac on 2025/6/23.
//
#include "testc.h"
void record_audio(void) {
int ret = 0;
char errors[1024] = {
0,
};
AVFormatContext *fmt_ctx = NULL;
// 读出来的数据存储到这个 packet 里面
AVPacket pkt;
int count = 0;
// [[video device]:[audio device]]
// char *devicename = ":2";
char *devicename = ":1";
// char *devicename = ":0";
// 设置日志级别
av_log_set_level(AV_LOG_DEBUG);
// 1 register audio device
avdevice_register_all();
// 2 get format
// const AVInputFormat *iformat = av_find_input_format("avfoundation");
const AVInputFormat *input_format = av_find_input_format("avfoundation");
AVDictionary *options = NULL;
av_dict_set(&options, "sample_rate", "44100", 0);
av_dict_set(&options, "channels", "2", 0);
// 3 open device
ret = avformat_open_input(&fmt_ctx, devicename, input_format, &options);
if (ret < 0) {
av_strerror(ret, errors, 1024);
printf(stderr, "Failed to open audio device [%d] %s\n", ret, errors);
return;
}
// AVPacket 使用之前要初始化
// av_init_packet(&pkt);
av_new_packet(&pkt, 512);
// ret = av_read_frame(fmt_ctx, &pkt);
while (count++ < 500) {
// read data from device
while ((ret = av_read_frame(fmt_ctx, &pkt)) == AVERROR(EAGAIN)) {
// 等待或处理其他任务(如非阻塞模式)
usleep(1000); // 避免忙等待
}
if (ret < 0 && ret != AVERROR_EOF) {
// 真实错误
fprintf(stderr, "Error reading packet: %s\n", av_err2str(ret));
} else if (ret == AVERROR_EOF) {
// 正常结束
printf("Reached end of file.\n");
} else {
// 成功读取到数据包
// 处理 pkt...
av_log(NULL, AV_LOG_INFO, "count = %d, ret = %d, pkt.size = %d\n",
count, ret, pkt.size);
// 每次使用完释放包
av_packet_unref(&pkt);
}
}
// close device and 释放上下文
avformat_close_input(&fmt_ctx);
printf("this is a c function\n");
av_log(NULL, AV_LOG_DEBUG, "hello world from av_log \n ");
return;
}
其中的 usleep
可以通过 man usleep
查看用法, 和引用的 c 头文件 #include <unistd.h>