func ConvertToPAM(opts pam.WriteOpts, pamPath, bamPath, baiPath string, bytesPerShard int64) error {
	if baiPath == "" {
		baiPath = bamPath + ".bai"
	}
	if pamPath == "" {
		return fmt.Errorf("Empty pam path")
	}
	if bytesPerShard <= 0 {
		return fmt.Errorf("Negative bytesPerShard: %v", bytesPerShard)
	}
	shards, e := generateShardBoundaries(bamPath, baiPath, bytesPerShard)
	if e != nil {
		return e
	}
	if e := pamutil.ValidateCoordRange(&opts.Range); e != nil {
		return e
	}
	if !opts.Range.EQ(gbam.UniversalRange) {
		return fmt.Errorf("WriteOpts.Range to ConvertFromBAM must be a universal range, but found %+v", opts)
	}
	vlog.Infof("%v: Creating %d shards: %+v", pamPath, len(shards), shards)
	if e := pamutil.Remove(pamPath); e != nil {
		return e
	}
	var totalRecs int64
	bam := bamprovider.BAMProvider{Path: bamPath, Index: baiPath}
	err := traverse.Each(len(shards), func(i int) error {
		shard := shards[i]
		nextShard := bamShardBound{
			rec: biopb.Coord{biopb.InfinityRefID, biopb.InfinityPos, 0},
			off: bgzf.Offset{math.MaxInt64, math.MaxUint16},
		}
		if i < len(shards)-1 {
			nextShard = shards[i+1]
		}
		nRecs, err := convertShard(opts, pamPath, &bam, shard, nextShard)
		atomic.AddInt64(&totalRecs, nRecs)
		return err
	})
	if e := bam.Close(); e != nil && err == nil {
		err = e
	}
	vlog.Infof("%v: Finished converting, written %d records, error %v", pamPath, totalRecs, err)
	return err
}
ConvertToPAM copies BAM to PAM.

bytesPerShard is specified in term of the input BAM file. For example, if you
have a 100GB BAM file and set bytesPerShard to 20GB, this function will
create roughly five (=100/20) PAM shards.
package converter
import (
	"fmt"
	"math"
	"runtime"
	"sort"
	"sync"
	"sync/atomic"
	"github.com/grailbio/base/errors"
	"github.com/grailbio/base/file"
	"github.com/grailbio/base/traverse"
	"github.com/grailbio/base/vcontext"
	"github.com/grailbio/bio/biopb"
	gbam "github.com/grailbio/bio/encoding/bam"
	"github.com/grailbio/bio/encoding/bamprovider"
	"github.com/grailbio/bio/encoding/pam"
	"github.com/grailbio/bio/encoding/pam/pamutil"
	"github.com/grailbio/hts/bam"
	"github.com/grailbio/hts/bgzf"
	"github.com/grailbio/hts/sam"
	"github.com/klauspost/compress/gzip"
	"v.io/x/lib/vlog"
)
type bamShardBound struct {
	rec biopb.Coord
	off bgzf.Offset
}
func generateShardBoundaries(bamPath, baiPath string, bytesPerShard int64) ([]bamShardBound, error) {
	ctx := vcontext.Background()
	bamIn, err := file.Open(ctx, bamPath)
	if err != nil {
		return nil, err
	}
	defer bamIn.Close(ctx)
	bamr, err := bam.NewReader(bamIn.Reader(ctx), 1)
	if err != nil {
		return nil, err
	}
	indexIn, err := file.Open(ctx, baiPath)
	if err != nil {
		return nil, err
	}
	defer indexIn.Close(ctx)
	index, err := gbam.ReadIndex(indexIn.Reader(ctx))
	if err != nil {
		return nil, err
	}
	allChunks := []bgzf.Offset{}  
	for _, chunks := range index.AllOffsets() {
		allChunks = append(allChunks, chunks...)
	}
	if len(allChunks) <= 0 {
		return nil, fmt.Errorf("%v: no chunk found in the index", bamPath)
	}
	sort.SliceStable(allChunks, func(i, j int) bool {
		c0 := allChunks[i]
		c1 := allChunks[j]
		if c0.File != c1.File {
			return c0.File < c1.File
		}
		return c0.Block < c1.Block
	})
	bamStat, err := bamIn.Stat(ctx)
	if err != nil {
		return nil, err
	}
	bounds := []bamShardBound{
		bamShardBound{
			rec: biopb.Coord{0, 0, 0},
			off: bgzf.Offset{0, 0},
		},
	}
	if bytesPerShard <= 0 {
		return bounds, nil
	}
	nextGoalOff := bytesPerShard
	for _, chunk := range allChunks[1:] {
		if chunk.File >= nextGoalOff {
			coord, err := gbam.GetCoordAtOffset(bamr, chunk)
			if err != nil {
				vlog.Panic(err)
				continue
			}
			if len(bounds) > 0 {
				cmp := coord.Compare(bounds[len(bounds)-1].rec)
				if cmp < 0 {
					vlog.Panicf("BAM record not sorted properly: %+v %+v", coord, bounds)
				}
				if cmp == 0 {
					continue
				}
			}
			bounds = append(bounds, bamShardBound{rec: coord, off: chunk})
			nextGoalOff = chunk.File + bytesPerShard
		}
	}
	vlog.Infof("Found %d chunk bounds (file size: %d, goal shard size: %d)", len(bounds), bamStat.Size(), bytesPerShard)
	return bounds, nil
}
func convertShard(opts pam.WriteOpts, pamPath string, in bamprovider.Provider, startShard, limitShard bamShardBound) (int64, error) {
	header, e := in.GetHeader()
	if e != nil {
		return 0, e
	}
	getRef := func(id int32) *sam.Reference {
		if id == biopb.UnmappedRefID {
			return nil
		}
		return header.Refs()[id]
	}
	iter := in.NewIterator(gbam.Shard{
		StartRef: getRef(startShard.rec.RefId),
		Start:    int(startShard.rec.Pos),
		EndRef:   getRef(limitShard.rec.RefId),
		End:      int(limitShard.rec.Pos),
	})
	opts.Range = biopb.CoordRange{Start: startShard.rec, Limit: limitShard.rec}
	w := pam.NewWriter(opts, header, pamPath)
	err := errors.Once{}
	var nRecs int64
	for iter.Scan() {
		rec := iter.Record()
		w.Write(rec)
		if w.Err() != nil {
			break
		}
		nRecs++
		sam.PutInFreePool(rec)
	}
	err.Set(iter.Close())
	err.Set(w.Close())
	vlog.Infof("%v: Finished converting [%+v, %+v) with %d recs read: %v",
		pamPath, startShard, limitShard, nRecs, err.Err())
	return nRecs, err.Err()
}