普通视图

发现新文章,点击刷新页面。
昨天 — 2025年6月29日首页

音视频学习笔记 02 读取数据

作者 blanks2020
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())。

  • 解决方法

    • 确保正确初始化输出上下文。

调试步骤

  1. 检查错误码的具体含义

    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")。

  2. 验证输入源

    • 确认 fmt_ctx 已通过 avformat_open_input() 成功打开。
    • 检查 fmt_ctx->streams 是否包含有效的流(如 fmt_ctx->nb_streams > 0)。
  3. 重试逻辑

    • 如果是非阻塞模式或实时流,可能需要循环调用 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>

仓库: gitee.com/dbafu/imooc…

❌
❌