Block的内存管理,看这里就够了

本文深入讲解Block的不同类型及其内存管理方式,包括NSGlobalBlock、NSStackBlock和NSMallocBlock的区别,并探讨如何避免循环引用的问题。

最近发现很多开发者对block的理解并不是很深,很多项目当中使用的时候多多少会有些问题,今天给大家详细讲讲block的内存管理, 主要从以下几个方面来讲:

  • 根据内存划分block的类型
  • block内存管理
  • 防止循环引用

Block类型

根据Block在内存中的位置,系统把Block分为3类:NSGlobalBlockNSStackBlock, NSMallocBlock;

  • NSGlobalBlock:位于内存全局区
  • NSStackBlock:位于内存栈区
  • NSMallocBlock:位于内存堆区

我们通过block引用不同的变量来

全局区block(NSGlobalBlock)

没有引用局部变量的block叫做NSGlobalBlock,如下实例:

//类型1:没有使用任何外部变量
-(void)test
{
    void (^gBlock1)(int , int ) =^(int a, int b){
        NSLog(@"a + b = %d", a+b);
    };

    NSLog(@"%@", gBlock1);
    //打印结果为:
    //<__NSGlobalBlock__: 0x1025e8110>
}

//类型2:使用全局变量

//全局变量
int a = 10;

-(void)test
{
    void (^gBlock)() = ^(){
        NSLog(@"%d", a);
    };

    NSLog(@"%@", gBlock);
    //输出结果为:
    //<__NSGlobalBlock__: 0x103676110>
}

栈区block(NSStackBlock)

引用了局部变量的block叫做NSStackBlock, 实例如下:

-(void)test
{
    //局部变量
     NSArray *arr = @[@"zhangsan", @"lisi"];

    void (^sBlock)() = ^(){
        NSLog(@"arr = %@", arr);
    };

    NSLog(@"%@", sBlock);
    //输出结果为:
    //<__NSStackBlock__: 0x7fff5bbf1a58>
}

PS:栈区block在方法返回后就会被释放,所以只能在方法内部使用,如果将他赋值给其他对象或者存储起来,后面使用时将会出现错误.

堆区Block(NSMallocBlock)

在非ARC下,我们一般不手动创建NSMallocBlock,我们把从栈区复制(copy)过来的block称为堆区block。实例如下:

-(void)test
{

    NSArray *arr = @[@"zhangsan", @"lisi"];

    //栈区block
    void (^sBlock)() = ^(){
        NSLog(@"arr = %@", arr);
    };
    NSLog(@"%@", sBlock);

    //堆区block
    void (^mBlock)() = [sBlock copy];
    NSLog(@"%@", mBlock);

    //输出结果为:
    //<__NSStackBlock__: 0x7fff59bf9a38>
    //<__NSMallocBlock__: 0x7fc173f0dd80>

}

Block内存管理

对block自身内存的管理

对于block,有两个内存管理方法:Block_copy, Block_release;Block_copycopy等效, Block_releaserelease等效;

不管是对block进行retian,copy,release,block的引用计数都不会增加,始终为1;

  • NSGlobalBlock:使用retain,copy, release都无效,block依旧存在全局区,且没有释放, 使用copyretian只是返回block的指针;

  • NSStackBlock:使用retain,release操作无效;栈区block会在方法返回后将block空间回收; 使用copy将栈区block复制到堆区,可以长久保留block的空间,以供后面的程序使用;

  • NSMallocBlock:支持retian,release,虽然block的引用计数始终为1,但内存中还是会对引用进行管理,使用retain引用+1, release引用-1; 对于NSMallocBlock使用copy之后不会产生新的block,只是增加了一次引用,类似于使用retian;

对引用变量的内存管理

在block中经常会用到外部变量/对象,如果这个block是存储在堆区,或者被复制到堆区,则对象对应的实例引用+1,当block释放后block的引用-1;

-(void)test
{

    NSArray *arr = @[@"zhangsan", @"lisi"];
    NSLog(@"arr.retianCount = %ld", arr.retainCount);

    //栈区block
    void (^sBlock)() = ^(){
        NSLog(@"arr = %@", arr);
    };
    //栈区block不会对引用的变量引用计数+1
    NSLog(@"arr.retianCount = %ld", arr.retainCount);


    //堆区block
    void (^mBlock)() = [sBlock copy];
    //复制到堆区后,引用计数+1
    NSLog(@"arr.retianCount = %ld", arr.retainCount);
}

循环引用

因为block中会对引用的对象进行持有(引用计数+1),从而导致相互持有引起循环引用;解决这种问题的方式是对引用变量使用修饰词__block或者__weak;

  • __block:在非ARC中使用,NSMallocBlock类型的block不会对__block修饰的的变量引用计数+1,从而消除循环引用;在ARC中使用__block无效
  • __weak:在ARC中使用,作用和__block一样,从而消除循环引用;在非ARC中不可以使用__weak;

防止循环引用案例:

//TestClass.h

@interface testClass : NSObject

@property (nonatomic, copy)void (^myBlock)(void);

@end

//TestClass.m

#define TestClassExample3 1

@implementation TestClass


-(void)dealloc
{
    NSLog(@"测试对象 被释放了。。。");

    [super dealloc];
}


-(instancetype)init
{
    self = [super init];
    if (self) {

#if TestClassExample1

        //会引起循环应用,当前对象无法被释放
        self.myBlock = ^(){
            //增加自己本身的引用计数
            [self doSomething];
        };

#elif TestClassExample2

        //在非ARC下有效,防止循环引用
        //在ARC下无效,会产生循环引用
        __block TestClass *weakSelf = self;
        self.myBlock = ^(){

            //在非ARC下不会增加self的引用计数
            [weakSelf doSomething];
        };

#elif TestClassExample3

        //在非ARC下无效,会产生循环引用
        //在ARC下有效,防止循环应用
        __weak TestClass *weakSelf = self;
        self.myBlock = ^(){

            //在非ARC下不会增加self的引用计数
            [weakSelf doSomething];
        };


#endif
    }

    return self;
}

-(void)doSomething
{
    NSLog(@"测试程序");
}


@end

//main.h

int main(int argc, char * argv[]) {
    @autoreleasepool {

        TestClass *tc = [[TestClass alloc] init];
        [tc release];
        tc = nil;
    }
}
static S32 _ctx_put_unit(avdm_context_t *ctx, unitpack_t *unitpack) { S32 ret = ERROR; U8 *base = NULL; /* 从该地址往后搜索可用buffer(可能绕圈) */ U32 tmp_len = 0; U32 unit_len = 0; /* unit需要分配的buffer的长度 */ BOOL block_start = FALSE; /* 当前unit是否为block的起始帧 */ if ((NULL == ctx) || (NULL == unitpack) || (NULL == unitpack->pack_addr[0]) || (0 == unitpack->len)) return ERROR; #ifdef VIDEO_STREAM_STATUS_CHECK if (is_local_video_type(ctx)) { video_stream_status_notify_process(ctx); } #endif pthread_rwlock_wrlock(&ctx->rwlock); if (is_local_video_type(ctx) || is_network_video_type(ctx)) { /* I帧作为GOP的起始帧 */ block_start = (unitpack->video_frame_type== TP_FRAME_TYPE_I #ifdef VIDEO_AVBR_ENABLE || unitpack->video_frame_type== TP_FRAME_TYPE_VIRTUAL_I #endif ); /* 如果是主码流的I帧,配置全局block_start */ if (AVDM_TYPE_MAIN == ctx->type && block_start) block_start_write_lock(); } else if (is_local_audio_type(ctx)) { if (g_ctx[AVDM_TYPE_MAIN] && g_ctx[AVDM_TYPE_MAIN]->state == CTX_STATE_RUNNING) { if (AVDM_BUFFER_IS_EMPTY(g_ctx[AVDM_TYPE_MAIN])) { /* 如果主码流还未收帧,丢弃先到的音频帧 */ AVDM_ERROR("drop audio frame when main queue is empty"); goto exit; } /* 如果主码流正在运行,让音频GOP与主码流进GOP进行同步 */ block_start_read_with_clear_lock(&block_start); } else { /* 环上第一帧或者每隔指定秒之后的帧 */ if (AVDM_BUFFER_IS_EMPTY(ctx) || ((unitpack->pts - ctx->ring->cur_bdesc->head_udesc->unit.pts) >= AVDM_AUDIO_GOP_TIME_SPAN_MICROSECONDS)) block_start = TRUE; else block_start = FALSE; } } else if (is_network_audio_type(ctx)) { /* 环上第一帧或者每隔指定秒之后的帧 */ if (AVDM_BUFFER_IS_EMPTY(ctx) || ((unitpack->pts - ctx->ring->cur_bdesc->head_udesc->unit.pts) >= AVDM_AUDIO_GOP_TIME_SPAN_MICROSECONDS)) { block_start = TRUE; } else { block_start = FALSE; } //AVDM_ERROR("unitpack->pts = %lld, block_start = %d", unitpack->pts, block_start); } else { AVDM_ERROR("type not supported"); ret = ERROR; goto exit; } if (block_start) { /* 锁帧老化策略 */ _ctx_aging_walkaround(ctx, unitpack->pts); /* * 在为新的block寻找空间之前,先将当前block进行打包处理, * 否则寻找可用buffer时,当前block和预录block都无法使用, * 可能会导致内存紧张,实际上最早的预录block可以释放 */ #ifndef KEEP_REST_P_FRAME ctx->drop_rest_frame = 0; #endif /* 当前block非码流首次收帧 */ if (DESC_STATE_FREE != ctx->ring->cur_bdesc->attr.state) { /* 由于音频GOP在主码流I帧时已经打包,该IF分支不会进来 */ if (DESC_STATE_FILLING == ctx->ring->cur_bdesc->attr.state) { if (AVDM_TYPE_AUDIO == ctx->type) { AVDM_ERROR("audio GOP should already packed"); } /* 如果这是主码流的I帧,先将音频block打包,让主码流GOP准备好时对应的音频GOP也已经打包完毕 */ if (AVDM_TYPE_MAIN == ctx->type) { __sync_pack_audio_block(ctx); block_start_unlock(); } /* call: 如果这是avdm_call, 先将音频block打包*/ if (AVDM_TYPE_VIDEO_CALL == ctx->type) { __sync_pack_audio_call_block(ctx); block_start_unlock(); } /* 将BLOCK打包并放到已完成的环上 */ ret = __pack_block(ctx, unitpack); if (ret) AVDM_ERROR("pack block failed"); /* 更新锁定的预录block */ ret = __lock_last_pre_block(ctx); if (ret) goto exit; /* last_used block已经准备好,通知(storage)取GOP */ if (ctx->ring->last_bdesc && (DESC_STATE_OCCUPIED == ctx->ring->last_bdesc->attr.state)) __notify_block_ready(ctx); } /* 当前的block指针设为下一个可用的block */ ret = __move_to_next_block(ctx); if (ret) goto exit; #ifdef VIDEO_AVBR_ENABLE /* AVBR开启时,把真实I帧GOP引用计数加1 */ if (_avbr_get_state() && ctx->type == AVDM_TYPE_MAIN) { _lock_last_i_frame_block(ctx); } #endif } else { if (AVDM_TYPE_MAIN == ctx->type) block_start_unlock(); } /* 当前unit指针已是被使用状态,将其指向下一个可用的unit */ /* warning: 先获取下一个unit描述符再搜索可用空间,否则可用空间如果将刚完成 的GOP释放,cur_udesc实际已经放到了ulist空闲链表状态为FREE,会导致udesc 没有从空闲链表中拿出就被使用 */ if (DESC_STATE_OCCUPIED == ctx->ring->cur_udesc->attr.state) { ret = __move_to_next_unit(ctx); if (ret) goto exit; } if (AVDM_BUFFER_IS_EMPTY(ctx)) base = ctx->buffer; else base = ctx->ring->last_bdesc->block.addr + ctx->ring->last_bdesc->block.len; /* 寻找可用的block内存,如果没有会退而求其次寻找放置I帧的内存,仍然没有则丢帧 */ unit_len = ALIGN(sizeof(FRAME_HEAD_S) + unitpack->len, AVDM_FRAME_ALIGN_SIZE); ret = __get_available_buffer(ctx, base, ctx->ring->block_max_size, unit_len); if (ret) goto exit; AVDM_ERROR("avdm%d new buffer offset is %#x, available len is %#x, potiental len is %#x", ctx->type, (U32)ctx->ring->bbuffer.addr - (U32)ctx->buffer, ctx->ring->bbuffer.available_len, ctx->ring->bbuffer.potential_len); /* * 释放前一个block到新内存结尾之间覆盖到的描述符及对应内存 * 如果只释放新内存覆盖的内存,可能出现旧buffer夹在新buffer * 之间的情况,内存无法形成环状难以管理 */ ret = __free_desc_before_new_buffer(ctx, base, ctx->ring->bbuffer.addr, ctx->ring->bbuffer.available_len); if (ret) goto exit; /* 使用新的内存填充当前unit内容 */ ctx->ring->cur_udesc->unit.addr = ctx->ring->bbuffer.addr; ctx->ring->cur_udesc->unit.len = unit_len; ret = __fill_current_unit(ctx, unitpack); if (ret) goto exit; /* 根据当前unit更新当前的block信息 */ __append_unit_to_block(ctx, ctx->ring->cur_bdesc, ctx->ring->cur_udesc, unitpack->video_frame_type); ctx->ring->cur_bdesc->attr.state = DESC_STATE_FILLING; /* unit环上最新的已完成unit更新为当前unit */ ctx->ring->last_udesc = ctx->ring->cur_udesc; /* last_used unit已经准备好,通知取帧 */ __notify_unit_ready(ctx); #ifdef AVDM_PRINT_TRACE __avdm_dump(ctx); #endif } else { #ifndef KEEP_REST_P_FRAME /* 若当前P帧前面有过丢弃P帧,则后面的P帧都不再打包 */ if (is_local_video_type(ctx) && ctx->drop_rest_frame) { AVDM_ERROR("drop P frame because previous P frame was droped"); ret = ERROR; goto exit; } #endif /* avdm视频码流第一次收帧为P帧,丢弃 */ if ((is_local_video_type(ctx) || is_network_video_type(ctx)) && AVDM_BUFFER_IS_EMPTY(ctx)) { AVDM_ERROR("drop P frame when queue is empty"); ret = OK; goto exit; } /* 当前视频码流所在的block不是填充状态,丢弃P帧,一般出现在I帧被丢弃的时候 */ if ((is_local_video_type(ctx) || is_network_video_type(ctx)) && DESC_STATE_FILLING != ctx->ring->cur_bdesc->attr.state) { AVDM_ERROR("drop P frame when I frame has been dropped"); ret = OK; goto exit; } /* 当前unit指针已是被使用状态,将其指向下一个可用的unit */ if (DESC_STATE_OCCUPIED == ctx->ring->cur_udesc->attr.state) { ret = __move_to_next_unit(ctx); if (ret) goto exit; } base = ctx->ring->last_udesc->unit.addr + ctx->ring->last_udesc->unit.len; unit_len = ALIGN(sizeof(FRAME_HEAD_S) + unitpack->len, AVDM_FRAME_ALIGN_SIZE); if (ctx->ring->bbuffer.available_len < (unit_len + ctx->ring->cur_bdesc->block.len)) { /* 当前可用的长度不放置一个P帧(音频帧),更新潜在长度并检查是否足 */ __update_bbuffer(ctx); if (ctx->ring->bbuffer.potential_len < (unit_len + ctx->ring->cur_bdesc->block.len)) { #ifndef KEEP_REST_P_FRAME /* 丢弃当前block剩下的所有P帧,避免跳帧导致花屏 */ if (is_local_video_type(ctx)) ctx->drop_rest_frame = 1; #endif ret = ERROR; //put audio buffer to avdm failed报错主要是因为available_len和potential_len太小导致 goto exit; } else { /* 释放出新的空间,策略是在block预设最大值范围内尽可能多的释放空间 */ if (ctx->ring->bbuffer.available_len >= ctx->ring->block_max_size) { /* 当前可用空间已经不小于block预设最大值,只释放当前新帧的空间 */ tmp_len = unit_len; } else { if (ctx->ring->bbuffer.potential_len < ctx->ring->block_max_size) { /* 潜在长度比block预设最大值要小,释放潜在长度空间 */ tmp_len = ctx->ring->bbuffer.potential_len - ctx->ring->cur_bdesc->block.len; } else { /* 潜在长度比block预设最大值要大,只释放到block预设最大值空间 */ tmp_len = ctx->ring->block_max_size - ctx->ring->cur_bdesc->block.len; } } __free_desc_within_buffer(ctx, base, tmp_len); ctx->ring->bbuffer.available_len = ctx->ring->cur_bdesc->block.len + tmp_len; } } /* 使用新的内存填充当前unit内容 */ ctx->ring->cur_udesc->unit.addr = base; ctx->ring->cur_udesc->unit.len = unit_len; ret = __fill_current_unit(ctx, unitpack); if (ret) goto exit; /* 根据当前unit更新当前的block信息 */ __append_unit_to_block(ctx, ctx->ring->cur_bdesc, ctx->ring->cur_udesc, unitpack->video_frame_type); /* unit环上最新的已完成unit更新为当前unit */ ctx->ring->last_udesc = ctx->ring->cur_udesc; /* last_used unit已经准备好,通知取帧 */ __notify_unit_ready(ctx); } exit: if (is_local_audio_type(ctx) && (g_ctx[AVDM_TYPE_MAIN] && g_ctx[AVDM_TYPE_MAIN]->state == CTX_STATE_RUNNING)) block_start_unlock(); pthread_rwlock_unlock(&ctx->rwlock); return ret; }这里由于流AVDM_TYPE_MAIN和AVDM_TYPE_VIDEO_CALL不是同步开始有的,这个怎么开始block,是这里代码有问题所以取AVDM_TYPE_audio_CALL音频的时候ring->last_bdesc == NULL,所以ERR_FRAME_NOT_READY报错吗?
最新发布
10-30
你的问题非常关键,直指 AVDM 模块中多码流协同(主码流、辅码流、语音对讲音频等)的**同步机制设计缺陷或使用不当**。 你观察到的现象是: > ✅ 当尝试从 `AVDM_TYPE_AUDIO_CALL` 获取数据时,`ring->last_bdesc == NULL`,导致 `_ctx_attach_next_block()` 返回 `-ERR_FRAME_NOT_READY` > ❓ 原因是否是因为:`AVDM_TYPE_MAIN` 和 `AVDM_TYPE_VIDEO_CALL` 不是同步启动的?这个不同步会导致 `AUDIO_CALL` 无法正确开始 block 打包? --- ## ✅ 结论先行 > **是的!你分析得完全正确。** > > 如果 `AVDM_TYPE_MAIN`(主视频流)和 `AVDM_TYPE_VIDEO_CALL` / `AVDM_TYPE_AUDIO_CALL` **不是同步启动**,且系统依赖“主码流 I 帧”来触发 block 打包逻辑,则: > > - `AUDIO_CALL` 的 block 可能永远无法被正式提交(即不设置 `last_bdesc`) > - 导致消费者读取时看到 `ring->last_bdesc == NULL` > - 最终报错:`avdm%d lastest block not ready yet` 这是典型的 **跨流同步依赖未处理好** 的问题。 --- ## 🔍 深入分析代码逻辑 我们来看你提供的 `_ctx_put_unit()` 函数中的几个关键点: ### 🌐 1. 主码流 I 帧驱动音频打包(核心问题) ```c if (AVDM_TYPE_MAIN == ctx->type && block_start) block_start_write_lock(); ``` 以及音频部分: ```c if (is_local_audio_type(ctx)) { if (g_ctx[AVDM_TYPE_MAIN] && g_ctx[AVDM_TYPE_MAIN]->state == CTX_STATE_RUNNING) { if (AVDM_BUFFER_IS_EMPTY(g_ctx[AVDM_TYPE_MAIN])) { AVDM_ERROR("drop audio frame when main queue is empty"); goto exit; } block_start_read_with_clear_lock(&block_start); } else { // fallback 策略 } } ``` 👉 这说明:**本地音频流(包括 AUDIO_CALL)默认等待主码流的第一个 I 帧到来后才允许开始一个新的 GOP(block)** 这就是所谓的 “音频与主码流同步” 设计。 但如果: - 主码流还没收到 I 帧(比如摄像头初始化慢、网络延迟) - 或者根本没开主码流 - 或者开了但 `CTX_STATE_RUNNING` 没置位 那么: ➡️ 音频帧即使来了也会被丢弃 → `__pack_block()` 不执行 → `last_bdesc` 不更新 → `NULL` --- ### 📢 2. 对讲音频流 (`AVDM_TYPE_AUDIO_CALL`) 的特殊性 你提到的是 `AVDM_TYPE_AUDIO_CALL`,这类流通常是用于**实时语音对讲**,它的特点: | 特性 | 描述 | |------|------| | 实时性强 | 要求低延迟,不能等主码流 | | 独立采集 | 来自麦克风,与视频无关 | | 启动时机早 | 用户一点击“对讲”就应立刻发送 | 但当前代码却让它受制于 `AVDM_TYPE_MAIN` 的状态! 这显然不合理。 --- ### 🧱 3. block 提交的前提条件太苛刻 只有当一个 block 被 `__pack_block()` 并移动到下一个描述符后,才会更新: ```c ring->last_bdesc = cur_bdesc; // 在 __pack_block() 中完成 ``` 而 `__pack_block()` 只在以下情况调用: - `block_start == TRUE`(I 帧或定时器触发) - 并且前一个 block 处于 `FILLING` 状态 如果音频流因为等主码流而一直进不了 `block_start == TRUE` 分支,就不会打包,也就不会生成 `last_bdesc` --- ## 🚨 具体场景复现 假设: 1. 用户开启对讲功能 → 开始推送 `AVDM_TYPE_AUDIO_CALL` 2. 此时 `AVDM_TYPE_MAIN` 尚未启动(或未收到第一帧 I 帧) 3. 音频帧到达 `_ctx_put_unit(AVDM_TYPE_AUDIO_CALL, ...)` 4. 判断是否 `block_start`: ```c if (AVDM_BUFFER_IS_EMPTY(ctx) || ((pts - last_pts) >= AVDM_AUDIO_GOP_TIME_SPAN)) block_start = TRUE; ``` 5. 但由于 `g_ctx[AVDM_TYPE_MAIN]` 存在但为空,进入第一个分支失败,走 fallback: ```c block_start_read_with_clear_lock(&block_start); // 它会阻塞 block_start 设置为 true! ``` 6. 所以 `block_start == FALSE` 7. 进入 `else` 分支处理 P 帧 8. 但此时 buffer 是空的(第一次收帧),于是直接丢弃: ```c if ((is_local_video_type(ctx) || is_network_video_type(ctx)) && AVDM_BUFFER_IS_EMPTY(ctx)) { AVDM_ERROR("drop P frame when queue is empty"); ret = OK; goto exit; } ``` ⚠️ 注意:虽然是音频,但这里判断类型用了 video type 宏,可能误判! 9. 所有音频帧都被丢弃 → 从未调用 `__pack_block()` → `last_bdesc == NULL` --- ## ✅ 根本原因总结 | 问题 | 说明 | |------|------| | ❌ 跨流强耦合 | `AUDIO_CALL` 强依赖 `MAIN` 视频流的状态 | | ❌ 缺乏独立启动能力 | 即使设置了定时 GOP,仍被 `block_start_read_with_clear_lock()` 阻塞 | | ❌ 类型判断混乱 | 使用 `is_local_video_type()` 判断音频流?可能导致逻辑错误 | | ❌ 日志误导 | 显示“packed”,实际只是 unit 被填充,block 未提交 | --- ## ✅ 解决方案 ### ✅ 方案 1:为 `AUDIO_CALL` 添加独立的 block_start 判断逻辑 修改音频流判断逻辑,绕过主码流依赖: ```c else if (is_network_audio_type(ctx)) { /* 独立于主码流的定时机制 */ if (AVDM_BUFFER_IS_EMPTY(ctx)) { block_start = TRUE; // 第一帧就是起点 } else { U64 last_pts = ctx->ring->cur_bdesc->head_udesc->unit.pts; if ((unitpack->pts - last_pts) >= AVDM_AUDIO_GOP_TIME_SPAN_MICROSECONDS) { block_start = TRUE; } else { block_start = FALSE; } } // ✅ 关键:不要受主码流 lock 控制 } ``` 并注释掉或隔离这部分: ```c // if (g_ctx[AVDM_TYPE_MAIN] && ...) block_start_read_with_clear_lock(&block_start); ``` ### ✅ 方案 2:增加配置项,允许音频流独立运行 定义标志位: ```c #define AVDM_CTX_FLAG_INDEPENDENT_START (1 << 0) ``` 在初始化 `AVDM_TYPE_AUDIO_CALL` 时设置: ```c ctx->flags |= AVDM_CTX_FLAG_INDEPENDENT_START; ``` 然后在 `_ctx_put_unit()` 中判断: ```c if (is_network_audio_type(ctx)) { if (ctx->flags & AVDM_CTX_FLAG_INDEPENDENT_START) { // 使用独立逻辑,不依赖主码流 block_start = ...; } else { // 老逻辑:依赖主码流同步 } } ``` ### ✅ 方案 3:确保至少有一次 block 被成功提交 在 `AUDIO_CALL` 第一次收到帧时强制开启 block: ```c if (AVDM_BUFFER_IS_EMPTY(ctx) && is_audio_call_type(ctx)) { block_start = TRUE; AVDM_INFO("Force start block for AUDIO_CALL"); } ``` 这样哪怕没有主码流也能启动。 --- ### ✅ 方案 4:修复潜在的类型判断 bug 检查宏定义: ```c #define is_local_video_type(ctx) ((ctx)->type == AVDM_TYPE_MAIN || (ctx)->type == AVDM_TYPE_SUB) #define is_local_audio_type(ctx) ((ctx)->type == AVDM_TYPE_AUDIO_MAIN || (ctx)->type == AVDM_TYPE_AUDIO_SUB) #define is_network_audio_type(ctx) ((ctx)->type == AVDM_TYPE_AUDIO_CALL) ``` 确保不要让 `AUDIO_CALL` 被误认为是视频流。 否则这段代码会错误地丢弃音频帧: ```c if ((is_local_video_type(ctx) || is_network_video_type(ctx)) && AVDM_BUFFER_IS_EMPTY(ctx)) { AVDM_ERROR("drop P frame when queue is empty"); // 错误应用于音频! goto exit; } ``` ✅ 应改为更精确的判断: ```c if ((is_local_video_type(ctx) || is_network_video_type(ctx)) && AVDM_BUFFER_IS_EMPTY(ctx) && unitpack->video_frame_type != TP_FRAME_TYPE_I) { AVDM_ERROR("drop P frame when queue is empty"); goto exit; } ``` 或者把音频单独处理。 --- ## ✅ 如何验证? 添加日志: ```c AVDM_INFO("Stream %d: unit received, block_start=%d, buffer_empty=%d, pts_diff=%lld", ctx->type, block_start, AVDM_BUFFER_IS_EMPTY(ctx), AVDM_BUFFER_IS_EMPTY(ctx) ? 0 : (unitpack->pts - ctx->ring->cur_bdesc->head_udesc->unit.pts)); ``` 查看: - 是否 `block_start == TRUE` 被正确设置 - 是否进入了 `__pack_block()` - 是否有 `__notify_block_ready()` 调用 - 最终 `ring->last_bdesc != NULL` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值